/* * ome.dsl.SaxReader * * Copyright 2006 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.dsl; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * reads semantic-type-xml and produces a Set of SemanticType objects. Most * logic is passed off to the {@link ome.dsl.SemanticType ST} and * {@link ome.dsl.Property Property} classes. */ public class SaxReader { /** input file */ URL xmlFile; /** handler which collects all types and properties from the input file */ DSLHandler handler; /** SAXparser which does the actualy processing */ javax.xml.parsers.SAXParser parser; public SaxReader(String profile, File file) { this(file, new DSLHandler(profile)); } public SaxReader(File file, DSLHandler dslHandler) { handler = dslHandler; try { xmlFile = file.toURI().toURL(); } catch (MalformedURLException e) { throw new RuntimeException("Error determining file's path:" + file + " :\n" + e.getMessage(), e); } init(); } private void init() { try { SAXParserFactory factory = SAXParserFactory.newInstance(); parser = factory.newSAXParser(); // XMLReader reader = parser.getXMLReader(); } catch (Exception e) { throw new RuntimeException("Error setting up SaxReader :\n" + e.getMessage(), e); } } /** parses file and returns types */ public void parse() { try { parser.parse(xmlFile.getPath(), handler); } catch (Exception e) { throw new RuntimeException("Error parsing " + xmlFile + " :\n" + e.getMessage(), e); } } public List<SemanticType> process() { return handler.process(); } } class DSLHandler extends DefaultHandler { private static Log log = LogFactory.getLog(DSLHandler.class); // Turns output on/off public boolean verbose = false; // Indention for output private String depth = ""; // For handling private final Map<String, SemanticType> types = new HashMap<String, SemanticType>(); private SemanticType type; private Property property; private final String profile; DSLHandler(String profile) { this.profile = profile; } /** dispatches to output (printing) and handling (object-creation) routines */ @Override public void startElement(String arg0, String arg1, String element, Attributes attrs) throws SAXException { if (verbose) { outputStart(element, attrs); } handleEntry(element, attrs); super.startElement(arg0, arg1, element, attrs); } /** dispatches to output (printing) and handling (object-creation) routines */ @Override public void endElement(String arg0, String arg1, String element) throws SAXException { super.endElement(arg0, arg1, element); handleExit(element); if (verbose) { outputStop(element); } } /** creates a new type or property based on element name */ private void handleEntry(String element, Attributes attrs) { if (Property.FIELDS.contains(element)) { if (null != property) { throw new IllegalStateException("Trying to enter property " + element + " from within property" + property); } if (null == type) { throw new IllegalStateException("Trying to create property " + element + " without a type!"); } property = Property.makeNew(element, type, attrs2props(attrs)); } else if ("properties".equals(element)) { // ok. these usually contains lots of properties } else if (SemanticType.TYPES.contains(element)) { if (null != type) { throw new IllegalStateException("Trying to enter type " + element + " from within type " + type); } type = SemanticType.makeNew(profile, element, attrs2props(attrs)); } else if ("types".equals(element)) { // also ok. } else { log.debug("Deprecated: In the future elements of type " + element + " will be considered an error."); } } /** checks to see that after type creation, the model is in a valid state */ private void handleExit(String element) { if (Property.FIELDS.contains(element)) { if (null == property) { throw new IllegalStateException( "Exiting non-extant property!\n" + "Element:" + element + "\nType:" + type + "\nProperty:" + property); } if (null == type) { throw new IllegalStateException("Inside of non-extant type!!\n" + "Element:" + element + "\nType:" + type); } property.validate(); type.getProperties().add(property); property = null; } else if (SemanticType.TYPES.contains(element)) { if (null == type) { throw new IllegalStateException("Exiting non-extent type!\n" + "Element:" + element + "\nType:" + type); } type.validate(); types.put(type.getId(), type); type = null; } } /** * Initial processing. * */ public List<SemanticType> process() { /* * Handles the various link ups for annotations. (Possibly temporary) * This creates new types and therefore should come first. */ Set<SemanticType> additions = new HashSet<SemanticType>(); for (String id : types.keySet()) { SemanticType t = types.get(id); if (t.getAnnotated() != null && t.getAnnotated()) { String newId = "ome.model.annotations." + t.getShortname() + "AnnotationLink"; SemanticType ann = types .get("ome.model.annotations.Annotation"); // Create link Properties linkP = new Properties(); linkP.setProperty("id", newId); LinkType l = new LinkType(profile, linkP); Properties parentP = new Properties(); parentP.setProperty("type", t.getId()); LinkParent lp = new LinkParent(l, parentP); lp.validate(); l.getProperties().add(lp); Properties childP = new Properties(); childP.setProperty("type", ann.getId()); LinkChild lc = new LinkChild(l, childP); lc.validate(); l.getProperties().add(lc); l.validate(); additions.add(l); // And now create the links to the link Properties clP = new Properties(); clP.setProperty("name", "annotationLinks"); clP.setProperty("type", newId); clP.setProperty("target", ann.getId()); ChildLink cl = new ChildLink(t, clP); cl.setBidirectional(false); cl.validate(); t.getProperties().add(cl); } } for (SemanticType semanticType : additions) { types.put(semanticType.getId(), semanticType); } /** * Now handling the named and described attributes in the * code-generation to free up the templates from the responsibility */ for (SemanticType namedOrDescribed : types.values()) { Boolean named = namedOrDescribed.getNamed(); Boolean descrd = namedOrDescribed.getDescribed(); if (named != null && named.booleanValue()) { Properties p = new Properties(); p.setProperty("name", "name"); p.setProperty("type", "string"); RequiredField r = new RequiredField(namedOrDescribed, p); namedOrDescribed.getProperties().add(r); } if (descrd != null && descrd.booleanValue()) { Properties p = new Properties(); p.setProperty("name", "description"); p.setProperty("type", "text"); OptionalField o = new OptionalField(namedOrDescribed, p); namedOrDescribed.getProperties().add(o); } } /* * Example: Pixels: <zeromany name="thumbnails" * type="ome.model.display.Thumbnail" inverse="pixels"/> Thumnail: * <required name="pixels" type="ome.model.core.Pixels"/> * * We want Thumbnail.pixels to be given the inverse "thumbnails" * * This only holds so long as there is only one link from a given type * to another given type, which does *not* hold true for * AnnotationAnnotationLinks. Therefore here we have a WORKAROUND. */ for (String id : types.keySet()) { // "ome...Pixels" SemanticType t = types.get(id); // Pixels for (Property p : t.getProperties()) { // thumbnails if (!handleLink(p)) { // WORKAROUND String rev = p.getType(); // "ome...Thumbnail" String inv = p.getInverse(); // "pixels" if (inv != null) { if (types.containsKey(rev)) { SemanticType reverse = types.get(rev); // Thumbnail for (Property inverse : reverse.getProperties()) { if (inverse.getType().equals(id)) { // "ome...Pixels" inverse.setInverse(p.getName()); } } } } } } } /* * Another post-processing step, which checks links for bidirectionality */ for (String id : types.keySet()) { SemanticType t = types.get(id); for (Property p : t.getProperties()) { // thumbnails if (p instanceof AbstractLink) { AbstractLink link = (AbstractLink) p; String targetId = link.getTarget(); SemanticType target = types.get(targetId); if (target == null) { throw new RuntimeException("No type " + targetId + " found as target of " + link); } boolean found = false; for (Property p2 : target.getProperties()) { if (id.equals(p2.getTarget())) { found = true; break; } } if (!found) { link.setBidirectional(Boolean.FALSE); } } } } /* * Check for all ordered relationships and apply unique constraints */ for (String id : types.keySet()) { SemanticType t = types.get(id); for (Property property : t.getClassProperties()) { if (property instanceof ManyZeroField) { Boolean ord = property.getOrdered(); if (ord != null && ord.booleanValue()) { String name = property.getName(); t.getUniqueConstraints().add( String .format("\"%s\",\"%s_index\"", name, name)); } } } } /* * Similarly apply UNIQUE (parent, child) to all links */ for (String id : types.keySet()) { SemanticType t = types.get(id); if (t instanceof LinkType) { LinkType link = (LinkType) t; if (link.getGlobal()) { link.getUniqueConstraints().add("\"parent\",\"child\""); } else { link.getUniqueConstraints().add("\"parent\",\"child\",\"owner_id\""); } } } /** * Each property is given its an actual * {@link SemanticType implementation which it belongs to and points to */ for (SemanticType semanticType : types.values()) { for (Property property : semanticType.getPropertyClosure()) { SemanticType target = types.get(property.getType()); property.setActualTarget(target); SemanticType currentType = semanticType; SemanticType actualType = semanticType; while (currentType != null) { List<Property> classProperties = currentType.getClassProperties(); if (classProperties.contains(property)) { actualType = currentType; break; } String superclass = currentType.getSuperclass(); currentType = superclass == null ? null : types.get(currentType.getSuperclass()); } property.setActualType(actualType); } } /* * Final post-processing step. Each semantic type should be given it's * finalized superclass instance as well as its Details property. */ for (String id : types.keySet()) { SemanticType t = types.get(id); String superclass = t.getSuperclass(); if (superclass != null) { SemanticType s = types.get(superclass); t.setActualSuperClass(s); } else { t.getProperties().add(new DetailsField(t, new Properties())); } } return new ArrayList<SemanticType>(types.values()); } private boolean handleLink(Property p) { if (!p.getIsLink()) { return false; } String name = p.getName(); if (!name.equals("child") && name.equals("parent")) { return false; } // For links, the inverse was already set by the constructor return true; } /** simple outputting routine with indention */ private void outputStart(String element, Attributes attrs) { if (log.isDebugEnabled()) { log.debug(depth + element + "("); } for (int i = 0; i < attrs.getLength(); i++) { String attr = attrs.getQName(i); String value = attrs.getValue(i); if (log.isDebugEnabled()) { log.debug(" " + attr + "=\"" + value + "\" "); } } if (log.isDebugEnabled()) { log.debug("): "); } depth += " "; } /** reduces indention for output */ private void outputStop(String element) { depth = depth.substring(2); } /** converts xml attributes to java.util.Properties */ private Properties attrs2props(Attributes attrs) { Properties p = new Properties(); for (int i = 0; i < attrs.getLength(); i++) { String key = attrs.getQName(i); String value = attrs.getValue(i); p.put(key, value); } return p; } }