Updated copyright dates.
[trakem2.git] / ini / trakem2 / persistence / TMLHandler.java
blobbdac1c5c5792c54e63627183830a5f997d02d32f
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.persistence;
25 import ij.IJ;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Iterator;
31 import java.io.File;
33 import ini.trakem2.tree.*;
34 import ini.trakem2.display.*;
35 import ini.trakem2.utils.*;
36 import ini.trakem2.Project;
38 import org.xml.sax.helpers.DefaultHandler;
39 import org.xml.sax.SAXException;
40 import org.xml.sax.SAXParseException;
41 import org.xml.sax.InputSource;
42 import org.xml.sax.Attributes;
44 import mpicbg.trakem2.transform.CoordinateTransform;
45 import mpicbg.trakem2.transform.CoordinateTransformList;
47 /** Creates the project objects from an XML file (TrakEM2 Markup Language Handler). */
48 public class TMLHandler extends DefaultHandler {
50 private LayerThing root_lt = null;
51 private ProjectThing root_pt = null;
52 private TemplateThing root_tt = null;
53 private TemplateThing project_tt = null;
54 private TemplateThing template_layer_thing = null;
55 private TemplateThing template_layer_set_thing = null;
56 private Project project = null;
57 private LayerSet layer_set = null;
58 private final FSLoader loader;
59 private boolean skip = false;
60 private String base_dir;
61 private String xml_path;
62 /** Stores the object and its String with coma-separated links, to construct links when all objects exist. */
63 private HashMap ht_links = new HashMap();
65 private Thing current_thing = null;
66 private ArrayList al_open = new ArrayList();
67 private ArrayList al_layers = new ArrayList();
68 private ArrayList al_layer_sets = new ArrayList();
69 private ArrayList al_displays = new ArrayList(); // contains HashMap instances with display data.
70 /** To accumulate Displayable types for relinking and assigning their proper layer. */
71 private HashMap ht_displayables = new HashMap();
72 private HashMap ht_zdispl = new HashMap();
73 private HashMap ht_oid_pt = new HashMap();
74 private HashMap ht_pt_expanded = new HashMap();
75 private Ball last_ball = null;
76 private AreaList last_area_list = null;
77 private Dissector last_dissector = null;
78 private Patch last_patch = null;
79 private Displayable last_displayable = null;
80 private CoordinateTransformList last_ct_list = null;
81 private boolean open_displays = true;
84 /** @param path The XML file that contains the project data in XML format.
85 * @param loader The FSLoader for the project.
87 public TMLHandler(final String path, FSLoader loader) {
88 this.loader = loader;
89 this.base_dir = path.substring(0, path.lastIndexOf(File.separatorChar) + 1);
90 this.xml_path = path;
91 final TemplateThing[] tt = DTDParser.extractTemplate(path);
92 if (null == tt) {
93 Utils.log("TMLHandler: can't read DTD in file " + path);
94 loader = null;
95 return;
97 // debug:
99 Utils.log("ROOTS #####################");
100 for (int k=0; k < tt.length; k++) {
101 Utils.log2("tt root " + k + ": " + tt[k]);
104 this.root_tt = tt[0];
105 // create LayerThing templates
106 this.template_layer_thing = new TemplateThing("layer");
107 this.template_layer_set_thing = new TemplateThing("layer set");
108 this.template_layer_set_thing.addChild(this.template_layer_thing);
109 this.template_layer_thing.addChild(this.template_layer_set_thing);
110 // create Project template
111 this.project_tt = new TemplateThing("project");
112 project_tt.addChild(this.root_tt);
113 project_tt.addAttribute("title", "Project");
116 public boolean isUnreadable() {
117 return null == loader;
120 /** returns 4 objects packed in an array:
121 <pre>
122 [0] = root TemplateThing
123 [1] = root ProjectThing (contains Project instance)
124 [2] = root LayerThing (contains the top-level LayerSet)
125 [3] = expanded states of all ProjectThing objects
126 </pre>
127 * <br />
128 * Also, triggers the reconstruction of links and assignment of Displayable objects to their layer.
130 public Object[] getProjectData(final boolean open_displays) {
131 if (null == project) return null;
132 this.open_displays = open_displays;
133 // 1 - Reconstruct links using ht_links
134 // Links exist between Displayable objects.
135 for (Iterator it = ht_displayables.values().iterator(); it.hasNext(); ) {
136 Displayable d = (Displayable)it.next();
137 Object olinks = ht_links.get(d);
138 if (null == olinks) continue; // not linked
139 String[] links = ((String)olinks).split(",");
140 Long lid = null;
141 for (int i=0; i<links.length; i++) {
142 try {
143 lid = new Long(links[i]);
144 } catch (NumberFormatException nfe) {
145 Utils.log2("Ignoring incorrectly formated link '" + links[i] + "' for ob " + d);
146 continue;
148 Displayable partner = (Displayable)ht_displayables.get(lid);
149 if (null != partner) d.link(partner, false);
150 else Utils.log("TMLHandler: can't find partner with id=" + links[i] + " for Displayable with id=" + d.getId());
154 // 1.2 - Reconstruct linked properties
155 for (final Map.Entry<Displayable,Map<Long,Map<String,String>>> lpe : all_linked_props.entrySet()) {
156 final Displayable origin = lpe.getKey();
157 for (final Map.Entry<Long,Map<String,String>> e : lpe.getValue().entrySet()) {
158 final Displayable target = (Displayable)ht_displayables.get(e.getKey());
159 if (null == target) {
160 Utils.log("Setting linked properties for origin " + origin.getId() + ":\n\t* Could not find target displayable #" + e.getKey());
161 continue;
163 origin.setLinkedProperties(target, e.getValue());
167 // 2 - Add Displayable objects to ProjectThing that can contain them
168 for (Iterator it = ht_oid_pt.entrySet().iterator(); it.hasNext(); ) {
169 Map.Entry entry = (Map.Entry)it.next();
170 Long lid = (Long)entry.getKey();
171 ProjectThing pt = (ProjectThing)entry.getValue();
172 Object od = ht_displayables.get(lid);
173 //Utils.log("==== processing: Displayable [" + od + "] vs. ProjectThing [" + pt + "]");
174 if (null != od) {
175 ht_displayables.remove(lid);
176 pt.setObject(od);
177 } else {
178 Utils.log("#### Failed to find a Displayable for ProjectThing " + pt + " #####");
182 // debug:
184 for (Iterator it = al_layer_sets.iterator(); it.hasNext(); ) {
185 LayerSet ls = (LayerSet)it.next();
186 Utils.log2("ls #id " + ls.getId() + " size: " +ls.getLayers().size());
190 // 3 - Assign a layer pointer to ZDisplayable objects
191 for (Iterator it = ht_zdispl.values().iterator(); it.hasNext(); ) {
192 ZDisplayable zd = (ZDisplayable)it.next();
193 //zd.setLayer((Layer)zd.getLayerSet().getLayers().get(0));
194 zd.setLayer(zd.getLayerSet().getLayer(0));
197 // 3 - Create displays for later
198 HashMap ht_lid = new HashMap();
199 for (Iterator it = al_layers.iterator(); it.hasNext(); ) {
200 Layer layer = (Layer)it.next();
201 ht_lid.put(new Long(layer.getId()), layer);
203 for (Iterator it = al_displays.iterator(); it.hasNext(); ){
204 HashMap ht_attributes = (HashMap)it.next();
205 Object ob = ht_attributes.get("layer_id");
206 if (null != ob) {
207 Object lob = ht_lid.get(new Long((String)ob));
208 if (null != lob) {
209 new Display(project, Long.parseLong((String)ht_attributes.get("id")), (Layer)lob, ht_attributes);
214 // debug:
215 //root_tt.debug("");
216 //root_pt.debug("");
217 //root_lt.debug("");
219 return new Object[]{root_tt, root_pt, root_lt, ht_pt_expanded};
222 private int counter = 0;
224 public void startElement(String namespace_URI, String local_name, String qualified_name, Attributes attributes) throws SAXException {
225 if (null == loader) return;
227 //Utils.log2("startElement: " + qualified_name);
228 this.counter++;
229 Utils.showStatus("Loading " + counter, false);
231 try {
232 // failsafe:
233 qualified_name = qualified_name.toLowerCase();
235 HashMap ht_attributes = new HashMap();
236 for (int i=attributes.getLength() -1; i>-1; i--) {
237 ht_attributes.put(attributes.getQName(i).toLowerCase(), attributes.getValue(i));
239 // get the id, which whenever possible it's the id of the encapsulating Thing object. The encapsulated object id is the oid
240 // The type is specified by the qualified_name
241 Thing thing = null;
242 if (0 == qualified_name.indexOf("t2_")) {
243 if (qualified_name.equals("t2_display")) {
244 if (open_displays) al_displays.add(ht_attributes); // store for later, until the layers exist
245 } else {
246 // a Layer, LayerSet or Displayable object
247 thing = makeLayerThing(qualified_name, ht_attributes);
248 if (null != thing) {
249 if (null == root_lt && thing.getObject() instanceof LayerSet) {
250 root_lt = (LayerThing)thing;
254 } else if (qualified_name.equals("project")) {
255 if (null != this.root_pt) {
256 Utils.log("WARNING: more than one project definitions.");
257 return;
259 // Create the project
260 this.project = new Project(Long.parseLong((String)ht_attributes.remove("id")), (String)ht_attributes.remove("title"));
261 this.project.setTempLoader(this.loader); // temp, but will be the same anyway
262 this.project.parseXMLOptions(ht_attributes);
263 this.project.addToDatabase(); // register id
264 // Add all unique TemplateThing types to the project
265 for (Iterator it = root_tt.getUniqueTypes(new HashMap()).values().iterator(); it.hasNext(); ) {
266 this.project.addUniqueType((TemplateThing)it.next());
268 this.project.addUniqueType(this.project_tt);
269 this.root_pt = new ProjectThing(this.project_tt, this.project, this.project);
270 this.root_pt.addAttribute("title", ht_attributes.get("title"));
271 // Add a project pointer to all template things
272 this.root_tt.addToDatabase(this.project);
273 thing = root_pt;
274 } else if (qualified_name.startsWith("ict_transform")) {
275 makeCoordinateTransform(qualified_name, ht_attributes);
276 } else if (!qualified_name.equals("trakem2")) {
277 // Any abstract object
278 thing = makeProjectThing(qualified_name, ht_attributes);
280 if (null != thing) {
281 // get the previously open thing and add this new_thing to it as a child
282 int size = al_open.size();
283 if (size > 0) {
284 Thing parent = (Thing)al_open.get(size -1);
285 parent.addChild(thing);
286 //Utils.log2("Adding child " + thing + " to parent " + parent);
288 // add the new thing as open
289 al_open.add(thing);
291 } catch (Exception e) {
292 IJError.print(e);
293 skip = true;
296 public void endElement(String namespace_URI, String local_name, String qualified_name) {
297 if (null == loader) return;
298 if (skip) {
299 skip = false; // reset
300 return;
302 String orig_qualified_name = qualified_name;
303 //Utils.log2("endElement: " + qualified_name);
304 // iterate over all open things and find the one that matches the qualified_name, and set it closed (pop it out of the list):
305 qualified_name = qualified_name.toLowerCase().trim();
306 if (0 == qualified_name.indexOf("t2_")) {
307 qualified_name = qualified_name.substring(3);
310 for (int i=al_open.size() -1; i>-1; i--) {
311 Thing thing = (Thing)al_open.get(i);
312 if (thing.getType().toLowerCase().equals(qualified_name)) {
313 al_open.remove(thing);
314 break;
317 // terminate non-single clause objects
318 if (orig_qualified_name.equals("t2_area_list")) {
319 last_area_list.__endReconstructing();
320 last_area_list = null;
321 last_displayable = null;
322 } else if (orig_qualified_name.equals("ict_transform_list")) {
323 last_ct_list = null;
324 } else if (orig_qualified_name.equals("t2_patch")) {
325 last_patch = null;
326 last_displayable = null;
327 } else if (orig_qualified_name.equals("t2_ball")) {
328 last_ball = null;
329 } else if (orig_qualified_name.equals("t2_dissector")) {
330 last_dissector = null;
331 last_displayable = null;
332 } else if (in(orig_qualified_name, all_displayables)) {
333 last_displayable = null;
337 static private final String[] all_displayables = new String[]{"t2_area_list", "t2_patch", "t2_pipe", "t2_polyline", "t2_ball", "t2_label", "t2_dissector", "t2_profile"};
339 private final boolean in(final String s, final String[] all) {
340 for (int i=all.length-1; i>-1; i--) {
341 if (s.equals(all[i])) return true;
343 return false;
346 public void characters(char[] c, int start, int length) {}
348 public void fatalError(SAXParseException e) {
349 Utils.log("Fatal error: column=" + e.getColumnNumber() + " line=" + e.getLineNumber());
351 public void skippedEntity(String name) {
352 Utils.log("SAX Parser has skipped: " + name);
354 public void notationDeclaration(String name, String publicId, String systemId) {
355 Utils.log("Notation declaration: " + name + ", " + publicId + ", " + systemId);
357 public void warning(SAXParseException e) {
358 Utils.log("SAXParseException : " + e);
361 private ProjectThing makeProjectThing(String type, HashMap ht_attributes) {
362 try {
363 type = type.toLowerCase();
365 // debug:
366 //Utils.log2("TMLHander.makeProjectThing for type=" + type);
368 long id = -1;
369 Object sid = ht_attributes.get("id");
370 if (null != sid) {
371 id = Long.parseLong((String)sid);
372 ht_attributes.remove("id");
374 long oid = -1;
375 Object soid = ht_attributes.get("oid");
376 if (null != soid) {
377 oid = Long.parseLong((String)soid);
378 ht_attributes.remove("oid");
380 Boolean expanded = new Boolean(false); // default: collapsed
381 Object eob = ht_attributes.get("expanded");
382 if (null != eob) {
383 expanded = new Boolean((String)eob);
384 ht_attributes.remove("expanded");
386 // abstract object, including profile_list
387 HashMap ht_attr = new HashMap();
388 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
389 Map.Entry entry = (Map.Entry)it.next();
390 String key = (String)entry.getKey();
391 String data = (String)entry.getValue();
392 ht_attr.put(key, new ProjectAttribute(this.project, -1, key, data));
393 //Utils.log2("putting key=[" + key + "]=" + data);
395 ProjectThing pt = new ProjectThing(this.project.getTemplateThing(type), this.project, id, type, null, ht_attr);
396 pt.addToDatabase();
397 ht_pt_expanded.put(pt, expanded);
398 // store the oid vs. pt relationship to fill in the object later.
399 if (-1 != oid) {
400 ht_oid_pt.put(new Long(oid), pt);
402 return pt;
403 } catch (Exception e) {
404 IJError.print(e);
406 // default:
407 return null;
410 private void addToLastOpenLayer(Displayable d) {
411 // find last open layer
412 for (int i = al_layers.size() -1; i>-1; i--) {
413 ((Layer)al_layers.get(i)).addSilently(d);
414 break;
417 private void addToLastOpenLayerSet(ZDisplayable zd) {
418 // find last open layer set
419 for (int i = al_layer_sets.size() -1; i>-1; i--) {
420 ((LayerSet)al_layer_sets.get(i)).addSilently(zd);
421 break;
425 final private Map<Displayable,Map<Long,Map<String,String>>> all_linked_props = new HashMap<Displayable,Map<Long,Map<String,String>>>();
427 private void putLinkedProperty(final Displayable origin, final HashMap ht_attributes) {
428 final String stid = (String)ht_attributes.get("target_id");
429 if (null == stid) {
430 Utils.log2("Can't setLinkedProperty to null target id for origin Displayable " + origin.getId());
431 return;
433 Long target_id;
434 try {
435 target_id = Long.parseLong(stid);
436 } catch (NumberFormatException e) {
437 Utils.log2("Unparseable target_id: " + stid + ", for origin " + origin.getId());
438 return;
440 String key = (String)ht_attributes.get("key");
441 String value = (String)ht_attributes.get("value");
442 if (null == key || null == value) {
443 Utils.log("Skipping linked property for Displayable " + origin.getId() + ": null key or value");
444 return;
446 key = key.trim();
447 value = value.trim();
448 if (0 == key.length() || 0 == value.length()) {
449 Utils.log("Skipping linked property for Displayable " + origin.getId() + ": empty key or value");
450 return;
452 Map<Long,Map<String,String>> linked_props = all_linked_props.get(origin);
453 if (null == linked_props) {
454 linked_props = new HashMap<Long,Map<String,String>>();
455 all_linked_props.put(origin, linked_props);
457 Map<String,String> p = linked_props.get(target_id);
458 if (null == p) {
459 p = new HashMap<String,String>();
460 linked_props.put(target_id, p);
462 p.put(key, value);
465 private LayerThing makeLayerThing(String type, HashMap ht_attributes) {
466 try {
467 type = type.toLowerCase();
468 if (0 == type.indexOf("t2_")) {
469 type = type.substring(3);
471 long id = -1;
472 Object sid = ht_attributes.get("id");
473 if (null != sid) id = Long.parseLong((String)sid);
474 long oid = -1;
475 Object soid = ht_attributes.get("oid");
476 if (null != soid) oid = Long.parseLong((String)soid);
478 if (type.equals("profile")) {
479 Profile profile = new Profile(this.project, oid, ht_attributes, ht_links);
480 profile.addToDatabase();
481 ht_displayables.put(new Long(oid), profile);
482 addToLastOpenLayer(profile);
483 last_displayable = profile;
484 return null;
485 } else if (type.equals("pipe")) {
486 Pipe pipe = new Pipe(this.project, oid, ht_attributes, ht_links);
487 pipe.addToDatabase();
488 ht_displayables.put(new Long(oid), pipe);
489 ht_zdispl.put(new Long(oid), pipe);
490 last_displayable = pipe;
491 addToLastOpenLayerSet(pipe);
492 return null;
493 } else if (type.equals("polyline")) {
494 Polyline pline = new Polyline(this.project, oid, ht_attributes, ht_links);
495 pline.addToDatabase();
496 last_displayable = pline;
497 ht_displayables.put(new Long(oid), pline);
498 ht_zdispl.put(new Long(oid), pline);
499 addToLastOpenLayerSet(pline);
500 return null;
501 } else if (type.equals("path")) {
502 last_area_list.__addPath((String)ht_attributes.get("d"));
503 return null;
504 } else if (type.equals("area")) {
505 last_area_list.__endReconstructing();
506 last_area_list.__startReconstructing(new Long((String)ht_attributes.get("layer_id")).longValue());
507 return null;
508 } else if (type.equals("area_list")) {
509 AreaList area = new AreaList(this.project, oid, ht_attributes, ht_links);
510 area.addToDatabase(); // why? This looks like an onion
511 last_area_list = area;
512 last_displayable = area;
513 ht_displayables.put(new Long(oid), area);
514 ht_zdispl.put(new Long(oid), area);
515 addToLastOpenLayerSet(area);
516 return null;
517 } else if (type.equals("ball_ob")) {
518 // add a ball to the last open Ball
519 if (null != last_ball) {
520 last_ball.addBall(Double.parseDouble((String)ht_attributes.get("x")),
521 Double.parseDouble((String)ht_attributes.get("y")),
522 Double.parseDouble((String)ht_attributes.get("r")),
523 Long.parseLong((String)ht_attributes.get("layer_id")));
525 return null;
526 } else if (type.equals("ball")) {
527 Ball ball = new Ball(this.project, oid, ht_attributes, ht_links);
528 ball.addToDatabase();
529 last_ball = ball;
530 last_displayable = ball;
531 ht_displayables.put(new Long(oid), ball);
532 ht_zdispl.put(new Long(oid), ball);
533 addToLastOpenLayerSet(ball);
534 return null;
535 } else if (type.equals("dd_item")) {
536 if (null != last_dissector) {
537 last_dissector.addItem(Integer.parseInt((String)ht_attributes.get("tag")),
538 Integer.parseInt((String)ht_attributes.get("radius")),
539 (String)ht_attributes.get("points"));
541 } else if (type.equals("label")) {
542 DLabel label = new DLabel(project, oid, ht_attributes, ht_links);
543 label.addToDatabase();
544 ht_displayables.put(new Long(oid), label);
545 addToLastOpenLayer(label);
546 last_displayable = label;
547 return null;
548 } else if (type.equals("patch")) {
549 Patch patch = new Patch(project, oid, ht_attributes, ht_links);
550 patch.addToDatabase();
551 ht_displayables.put(new Long(oid), patch);
552 addToLastOpenLayer(patch);
553 last_patch = patch;
554 last_displayable = patch;
555 return null;
556 } else if (type.equals("dissector")) {
557 Dissector dissector = new Dissector(this.project, oid, ht_attributes, ht_links);
558 dissector.addToDatabase();
559 last_dissector = dissector;
560 last_displayable = dissector;
561 ht_displayables.put(new Long(oid), dissector);
562 ht_zdispl.put(new Long(oid), dissector);
563 addToLastOpenLayerSet(dissector);
564 } else if (type.equals("layer")) {
565 // find last open LayerSet, if any
566 for (int i = al_layer_sets.size() -1; i>-1; i--) {
567 LayerSet set = (LayerSet)al_layer_sets.get(i);
568 Layer layer = new Layer(project, oid, ht_attributes);
569 layer.addToDatabase();
570 set.addSilently(layer);
571 al_layers.add(layer);
572 Object ot = ht_attributes.get("title");
573 return new LayerThing(template_layer_thing, project, -1, (null == ot ? null : (String)ot), layer, null, null);
575 } else if (type.equals("layer_set")) {
576 LayerSet set = new LayerSet(project, oid, ht_attributes, ht_links);
577 last_displayable = set;
578 set.addToDatabase();
579 ht_displayables.put(new Long(oid), set);
580 al_layer_sets.add(set);
581 addToLastOpenLayer(set);
582 Object ot = ht_attributes.get("title");
583 return new LayerThing(template_layer_set_thing, project, -1, (null == ot ? null : (String)ot), set, null, null);
584 } else if (type.equals("calibration")) {
585 // find last open LayerSet if any
586 for (int i = al_layer_sets.size() -1; i>-1; i--) {
587 LayerSet set = (LayerSet)al_layer_sets.get(i);
588 set.restoreCalibration(ht_attributes);
589 return null;
591 } else if (type.equals("prop")) {
592 // Add property to last created Displayable
593 if (null != last_displayable) {
594 last_displayable.setProperty((String)ht_attributes.get("key"), (String)ht_attributes.get("value"));
596 } else if (type.equals("linked_prop")) {
597 // Add linked property to last created Displayable. Has to wait until the Displayable ids have been resolved to instances.
598 if (null != last_displayable) {
599 putLinkedProperty(last_displayable, ht_attributes);
601 } else {
602 Utils.log2("TMLHandler Unknown type: " + type);
604 } catch (Exception e) {
605 IJError.print(e);
607 // default:
608 return null;
611 private void makeCoordinateTransform(String type, HashMap ht_attributes) {
612 try {
613 type = type.toLowerCase();
614 CoordinateTransform ct = null;
616 boolean is_list = false;
618 if (type.equals("ict_transform")) {
619 ct = (CoordinateTransform) Class.forName((String)ht_attributes.get("class")).newInstance();
620 ct.init((String)ht_attributes.get("data"));
621 } else if (type.equals("ict_transform_list")) {
622 is_list = true;
623 ct = new CoordinateTransformList();
626 if (null != ct) {
627 // Add it to the last CoordinateTransformList or, if absent, to the last Patch:
628 if (null != last_ct_list) last_ct_list.add(ct);
629 else if (null != last_patch) last_patch.setCoordinateTransformSilently(ct);
632 if (is_list) last_ct_list = (CoordinateTransformList)ct;
634 } catch (Exception e) {
635 IJError.print(e);