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.
23 package ini
.trakem2
.persistence
;
27 import java
.util
.ArrayList
;
28 import java
.util
.HashMap
;
30 import java
.util
.Iterator
;
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
) {
89 this.base_dir
= path
.substring(0, path
.lastIndexOf(File
.separatorChar
) + 1);
91 final TemplateThing
[] tt
= DTDParser
.extractTemplate(path
);
93 Utils
.log("TMLHandler: can't read DTD in file " + path
);
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:
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
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(",");
141 for (int i
=0; i
<links
.length
; i
++) {
143 lid
= new Long(links
[i
]);
144 } catch (NumberFormatException nfe
) {
145 Utils
.log2("Ignoring incorrectly formated link '" + links
[i
] + "' for ob " + d
);
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());
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 + "]");
175 ht_displayables
.remove(lid
);
178 Utils
.log("#### Failed to find a Displayable for ProjectThing " + pt
+ " #####");
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");
207 Object lob
= ht_lid
.get(new Long((String
)ob
));
209 new Display(project
, Long
.parseLong((String
)ht_attributes
.get("id")), (Layer
)lob
, ht_attributes
);
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);
229 Utils
.showStatus("Loading " + counter
, false);
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
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
246 // a Layer, LayerSet or Displayable object
247 thing
= makeLayerThing(qualified_name
, ht_attributes
);
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.");
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
);
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
);
281 // get the previously open thing and add this new_thing to it as a child
282 int size
= al_open
.size();
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
291 } catch (Exception e
) {
296 public void endElement(String namespace_URI
, String local_name
, String qualified_name
) {
297 if (null == loader
) return;
299 skip
= false; // reset
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
);
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")) {
324 } else if (orig_qualified_name
.equals("t2_patch")) {
326 last_displayable
= null;
327 } else if (orig_qualified_name
.equals("t2_ball")) {
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;
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
) {
363 type
= type
.toLowerCase();
366 //Utils.log2("TMLHander.makeProjectThing for type=" + type);
369 Object sid
= ht_attributes
.get("id");
371 id
= Long
.parseLong((String
)sid
);
372 ht_attributes
.remove("id");
375 Object soid
= ht_attributes
.get("oid");
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");
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
);
397 ht_pt_expanded
.put(pt
, expanded
);
398 // store the oid vs. pt relationship to fill in the object later.
400 ht_oid_pt
.put(new Long(oid
), pt
);
403 } catch (Exception e
) {
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
);
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
);
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");
430 Utils
.log2("Can't setLinkedProperty to null target id for origin Displayable " + origin
.getId());
435 target_id
= Long
.parseLong(stid
);
436 } catch (NumberFormatException e
) {
437 Utils
.log2("Unparseable target_id: " + stid
+ ", for origin " + origin
.getId());
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");
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");
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
);
459 p
= new HashMap
<String
,String
>();
460 linked_props
.put(target_id
, p
);
465 private LayerThing
makeLayerThing(String type
, HashMap ht_attributes
) {
467 type
= type
.toLowerCase();
468 if (0 == type
.indexOf("t2_")) {
469 type
= type
.substring(3);
472 Object sid
= ht_attributes
.get("id");
473 if (null != sid
) id
= Long
.parseLong((String
)sid
);
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
;
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
);
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
);
501 } else if (type
.equals("path")) {
502 last_area_list
.__addPath((String
)ht_attributes
.get("d"));
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());
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
);
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")));
526 } else if (type
.equals("ball")) {
527 Ball ball
= new Ball(this.project
, oid
, ht_attributes
, ht_links
);
528 ball
.addToDatabase();
530 last_displayable
= ball
;
531 ht_displayables
.put(new Long(oid
), ball
);
532 ht_zdispl
.put(new Long(oid
), ball
);
533 addToLastOpenLayerSet(ball
);
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
;
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
);
554 last_displayable
= patch
;
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
;
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
);
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
);
602 Utils
.log2("TMLHandler Unknown type: " + type
);
604 } catch (Exception e
) {
611 private void makeCoordinateTransform(String type
, HashMap ht_attributes
) {
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")) {
623 ct
= new CoordinateTransformList();
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
) {