/* * This file or a portion of this file is licensed under the terms of * the Globus Toolkit Public License, found in file ../GTPL, or at * http://www.globus.org/toolkit/download/license.html. This notice must * appear in redistributions of this file, with or without modification. * * Redistributions of this Software, with or without modification, must * reproduce the GTPL in: (1) the Software, or (2) the Documentation or * some other similar material which is provided with the Software (if * any). * * Copyright 1999-2004 University of Chicago and The University of * Southern California. All rights reserved. */ package org.griphyn.vdl.parser; import org.griphyn.vdl.classes.*; import org.griphyn.vdl.util.*; import org.xml.sax.*; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.Stack; /** * This class establishes the in-memory construction of Definition * objects read, and does the callback on the storage interface. * This class is the content handler for the XML document being parsed. * * @author Jens-S. Vöckler * @author Yong Zhao * @version $Revision$ * * @see Definition * @see DefinitionHandler */ public class VDLContentHandler implements ContentHandler { /** * This is the callback handler for ready Definitions. */ private DefinitionHandler m_callback; /** * This is the callback handler for a Definition outside Definitions. */ private FinalizerHandler m_finalize; /** * Keep the location within the document */ private Locator m_location; /** * A Hashmap to forward resolve namespaces that were encountered * during parsing. */ private Map m_forward; /** * A Hashmap to reverse resolve namespaces that were encountered * during parsing. */ private Map m_reverse; /** * The meta elements needs some very special handling (only bypassed). */ private int m_metamode = 0; /** * Count the depths of elements in the document */ private int m_depth = 0; /** * */ private Stack m_stack; /** * ctor. */ public VDLContentHandler() { this.m_callback = null; this.m_finalize = null; this.m_forward = new HashMap(); this.m_reverse = new HashMap(); } /** * Accessor: This function allows a different DefinitionHandler to * be set and used. * @param ds is the new callback object to handle each Definition * as it becomes ready. */ public void setDefinitionHandler( DefinitionHandler ds ) { this.m_callback = ds; } /** * Accessor: This function allows a different FinalizerHandler to * be set and used. Note, this cannot be used for the Definitions * elements. In order to reduce the memory footprint, the Definitions * element will not be maintained! * * @param fh is the new callback object to handle a single top-level * VDL element as it becomes ready. */ public void setFinalizerHandler( FinalizerHandler fh ) { this.m_finalize = fh; } /** * Obtains the document locator from the parser. The document location * can be used to print debug information, i.e the current location * (line, column) in the document. * * @param locator is the externally set current position */ public void setDocumentLocator( Locator locator ) { this.m_location = locator; } /** * This method specifies what to do when the parser is at the beginning * of the document. In this case, we simply print a message for debugging. */ public void startDocument() throws SAXException { this.m_depth = 0; this.m_stack = new Stack(); Logging.instance().log( "parser", 1, ">>> start of document >>>" ); } /** * This method specifies what to do when the parser reached the end * of the document. In this case, we simply print a message for debugging. */ public void endDocument() throws SAXException { Logging.instance().log( "parser", 1, "<<< end of document <<<" ); } /** * There is a prefix or namespace defined, put the prefix and its URI * in the HashMap. We can get the URI when the prefix is used here after. * * @param prefix the Namespace prefix being declared. * @param uri the Namespace URI the prefix is mapped to. */ public void startPrefixMapping( java.lang.String prefix, java.lang.String uri ) throws SAXException { String p = prefix == null ? null : new String(prefix); String u = uri == null ? null : new String(uri); Logging.instance().log( "parser", 2, "adding \"" + p + "\" <=> " + u ); if ( ! this.m_forward.containsKey(p) ) this.m_forward.put(p, new Stack()); ((Stack) this.m_forward.get(p)).push(u); if ( ! this.m_reverse.containsKey(u) ) this.m_reverse.put(u, new Stack()); ((Stack) this.m_reverse.get(u)).push(p); } /** * Out of the reach of the prefix, remove it from the HashMap. * * @param prefix is the prefix that was being mapped previously. */ public void endPrefixMapping( java.lang.String prefix ) throws SAXException { String u = (String) ((Stack) this.m_forward.get(prefix)).pop(); String p = (String) ((Stack) this.m_reverse.get(u)).pop(); Logging.instance().log( "parser", 2, "removed \"" + p + "\" <=> " + u ); } /** * Helper function to map prefixes correctly onto the elements. * * @param uri is the parser-returned URI that needs translation. * @return the correct prefix for the URI */ private String map( String uri ) { if ( uri == null || uri.length() == 0 ) return ""; Stack stack = (Stack) this.m_reverse.get(uri); String result = stack == null ? null : (String) stack.peek(); if ( result == null || result.length() == 0 ) return ""; else return result + ':'; } /** * This method defines the action to take when the parser begins to parse * an element. * * @param namespaceURI is the URI of the namespace for the element * @param localName is the element name without namespace * @param qName is the element name as it appears in the docment * @param atts has the names and values of all the attributes */ public void startElement( java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName, Attributes atts ) throws SAXException { Logging.instance().log( "parser", 3, "<" + map(namespaceURI) + localName + "> at " + m_location.getLineNumber() + ":" + m_location.getColumnNumber() ); m_depth++; // if in meta mode, skip filling in new elements if ( this.m_metamode == 0 ) { java.util.List names = new java.util.ArrayList(); java.util.List values = new java.util.ArrayList(); for ( int i=0; i < atts.getLength(); ++i ) { String name = new String( atts.getLocalName(i) ); String value = new String( atts.getValue(i) ); Logging.instance().log( "parser", 2, "attribute " + map(atts.getURI(i)) + name + "=\"" + value + "\"" ); names.add(name); values.add(value); } VDL object = createObject( qName, names, values ); if ( object != null ) m_stack.push( new StackElement( qName, object ) ); else throw new SAXException( "empty element while parsing" ); } // check for start of meta mode if ( localName.equals("meta") && namespaceURI.equals( Definitions.SCHEMA_NAMESPACE ) ) { // increase meta level this.m_metamode++; } } /** * The parser is at the end of an element. Each successfully and * completely parsed Definition will trigger a callback to the * registered DefinitionHandler. * * @param namespaceURI is the URI of the namespace for the element * @param localName is the element name without namespace * @param qName is the element name as it appears in the docment */ public void endElement( java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName ) throws SAXException { m_depth--; Logging.instance().log( "parser", 3, "</" + map(namespaceURI) + localName + "> at " + m_location.getLineNumber() + ":" + m_location.getColumnNumber() ); // check for end of meta mode if ( localName.equals("meta") && namespaceURI.equals( Definitions.SCHEMA_NAMESPACE ) ) { // decrease meta level --this.m_metamode; } // if in meta mode, skip filling in new elements, as they won't // belong to VDLx (or they might, but should be ignored). if ( this.m_metamode == 0 ) { StackElement tos = (StackElement) m_stack.pop(); if ( ! qName.equals(tos.m_name) ) { Logging.instance().log( "default", 0, "assertion failure" ); System.exit(1); } if ( ! m_stack.empty() ) { // add pieces to lower levels StackElement peek = (StackElement) m_stack.peek(); if ( ! setElementRelation( peek.m_name.charAt(0), peek.m_obj, tos.m_obj ) ) Logging.instance().log( "parser", 0, "Element " + tos.m_name + " does not fit into element " + peek.m_name ); } else { // run finalizer, if available if ( m_finalize != null ) m_finalize.store(tos.m_obj); } } } /** * This method is the callback function for characters in an element. * The element is expected to be of mixed content. * * @param ch are the characters from the XML document * @param start is the start position into the array * @param length is the amount of valid data in the array */ public void characters( char[] ch, int start, int length ) throws SAXException { String message = new String( ch, start, length ); if ( message.length() > 0 ) { if ( message.trim().length() == 0 ) Logging.instance().log( "parser", 3, "Characters: \' \' x " + message.length() ); else Logging.instance().log( "parser", 3, "Characters: \"" + message + "\"" ); if ( this.m_metamode == 0 ) { // insert text into the only elements possible to carry text StackElement tos = (StackElement) m_stack.peek(); if ( tos.m_obj instanceof Text ) { Text text = (Text) tos.m_obj; String old = text.getContent(); if ( old == null ) text.setContent(message); else text.setContent( old + message ); this.log( "Text", tos.m_name, message ); } else if ( tos.m_obj instanceof Meta ) { Meta meta = (Meta) tos.m_obj; String content = message.trim(); meta.addContent(content); this.log( "Meta", tos.m_name, content ); } } } } /** * Currently, ignorable whitespace will be ignored. * * @param ch are the characters from the XML document * @param start is the start position into the array * @param length is the amount of valid data in the array */ public void ignorableWhitespace( char[] ch, int start, int length ) throws SAXException { // not implemented } /** * Receive a processing instruction. Currently, we are just printing * a debug message that we received a PI. * * @param target the processing instruction target * @param data the processing instruction data, or null if none was supplied. * The data does not include any whitespace separating it from the target. */ public void processingInstruction( java.lang.String target, java.lang.String data ) throws SAXException { Logging.instance().log( "parser", 2, "processing instruction " + target + "=\"" + data + "\" was skipped!"); } /** * Receive a notification that an entity was skipped. Currently, we * are just printing a debug message to this fact. * * @param name The name of the skipped entity. If it is a parameter * entity, the name will begin with '%', and if it is the external DTD * subset, it will be the string "[dtd]". */ public void skippedEntity(java.lang.String name) throws SAXException { Logging.instance().log( "parser", 2, "entity " + name + " was skipped!"); } // // =================================================== our own stuff === // /** * Small helper method to bundle repetitive parameters in a template * for reporting progress. * * @param subject is the name of the XML element that is being scrutinized. * @param name is then name of the element we are working with. * @param value is the attribute value. */ private void log( String subject, String name, String value ) { if ( value == null ) value = new String(); Logging.instance().log( "filler", 3, subject + "." + name + "=\"" + value + "\"" ); } /** * Small helper method to bundle repetitive complaints in a template * for reporting progress. * * @param subject is the name of the XML element that is being scrutinized. * @param name is then name of the element we are working with. * @param value is the attribute value. */ private void complain( String subject, String name, String value ) { if ( value == null ) value = new String(); Logging.instance().log( "default", 0, "ignoring " + subject + '@' + name + "=\"" + value + '"', true ); } /** * This method determines the actively parsed element, creates the * Java object that corresponds to the element, and sets the member * variables with the values of the attributes of the element. * * @param e is the name of the element * @param names is a list of attribute names, as strings. * @param values is a list of attribute values, to match the key list. * @return A new VDL Java object, which may only be partly constructed. * @exception IllegalArgumentException if the element name is too short. */ protected VDL createObject( String e, java.util.List names, java.util.List values ) throws IllegalArgumentException { if ( e == null || e.length() < 1 ) throw new IllegalArgumentException("illegal element length"); // postcondition: string has content w/ length > 0 switch ( e.charAt(0) ) { // // A // case 'a': if ( e.equals("argument") ){ Argument argument = new Argument(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("name") ) { this.log( e, name, value ); argument.setName(value); } else { this.complain( e, name, value ); } } return argument; } // unknown return null; // // C // case 'c': if ( e.equals("call") ) { Call call = new Call(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("minIncludeVersion") ) { this.log( e, name, value ); call.setMinIncludeVersion(value); } else if ( name.equals("maxIncludeVersion") ) { this.log( e, name, value ); call.setMaxIncludeVersion(value); } else if ( name.equals("usesspace") ) { this.log( e, name, value ); call.setUsesspace(value); } else if ( name.equals("uses") ) { this.log( e, name, value ); call.setUses(value); } else { this.complain( e, name, value ); } } return call; } // unknown return null; // // D // case 'd': if ( e.equals("declare") ) { Declare declaration = new Declare("", 0); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("name") ) { this.log( e, name, value ); declaration.setName(value); } else if ( name.equals("container") ) { this.log( e, name, value ); declaration.setContainerType( VDLType.getContainerType(value) ); } else if ( name.equals("link") ) { this.log( e, name, value ); declaration.setLink( VDLType.getLinkType(value) ); } else { this.complain( e, name, value ); } } return declaration; } else if ( e.equals("definitions") ) { Definitions definitions = new Definitions(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("vdlns") ) { this.log( e, name, value ); definitions.setVdlns(value); } else if ( name.equals("version") ) { this.log( e, name, value ); definitions.setVersion(value); } else if (name.equals("schemaLocation") ) { // ignore } else { this.complain( e, name, value ); } } return definitions; } else if ( e.equals("derivation") ){ Derivation derivation= new Derivation(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("minIncludeVersion") ) { this.log( e, name, value ); derivation.setMinIncludeVersion(value); } else if ( name.equals("maxIncludeVersion") ) { this.log( e, name, value ); derivation.setMaxIncludeVersion(value); } else if ( name.equals("name") ) { this.log( e, name, value ); derivation.setName(value); } else if ( name.equals("namespace") ) { this.log( e, name, value ); derivation.setNamespace(value); } else if ( name.equals("description") ) { this.log( e, name, value ); derivation.setDescription(value); } else if ( name.equals("keyword") ) { this.log( e, name, value ); derivation.setKeyword(value); } else if ( name.equals("title") ) { this.log( e, name, value ); derivation.setTitle(value); } else if ( name.equals("url") ) { this.log( e, name, value ); derivation.setUrl(value); } else if ( name.equals("usesspace") ) { this.log( e, name, value ); derivation.setUsesspace(value); } else if ( name.equals("uses") ) { this.log( e, name, value ); derivation.setUses(value); } else if ( name.equals("version") ) { this.log( e, name, value ); derivation.setVersion(value); } else { this.complain( e, name, value ); } } return derivation; } // unknown return null; // // L // case 'l': if ( e.equals("lfn") ) { // remove const-lfn from here LFN lfn = new LFN(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("file") ) { this.log( e, name, value ); lfn.setFilename(value); } else if (name.equals("temporaryHint")) { this.log( e, name, value ); lfn.setTemporary( value ); } else if (name.equals("dontRegister")) { this.log( e, name, value ); lfn.setDontRegister( Boolean.valueOf(value).booleanValue() ); } else if (name.equals("optional")) { this.log( e, name, value ); lfn.setOptional( new Boolean(value).booleanValue() ); } else if (name.equals("dontTransfer")) { if ( value.equals("false") ) { this.log( e, name, value ); lfn.setDontTransfer( LFN.XFER_MANDATORY ); } else if ( value.equals("true") ) { this.log( e, name, value ); lfn.setDontTransfer( LFN.XFER_NOT ); } else if ( value.equals("optional") ) { this.log( e, name, value ); lfn.setDontTransfer( LFN.XFER_OPTIONAL ); } else { this.complain( e, name, value ); } } else if (name.equals("isTemporary")) { // deprecated work-around until phased out. this.log( e, name, value ); // FIXME: check for null Logging.instance().log( "app", 0, "using deprecated attribute \"isTemporary\" in LFN " + lfn.getFilename() ); boolean temp = Boolean.valueOf(value).booleanValue(); lfn.setDontRegister( temp ); lfn.setDontTransfer( temp ? LFN.XFER_NOT : LFN.XFER_MANDATORY ); } else if ( name.equals("link") ) { this.log( e, name, value ); int link = VDLType.getLinkType(value); lfn.setLink(link); } else { this.complain( e, name, value ); } } // for return lfn; } else if ( e.equals("local") ) { Local temp = new Local("", 0); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("name") ) { this.log( e, name, value ); temp.setName(value); } else if ( name.equals("container") ) { this.log( e, name, value ); temp.setContainerType( VDLType.getContainerType(value) ); } else if ( name.equals("link") ) { this.log( e, name, value ); temp.setLink( VDLType.getLinkType(value) ); } else { this.complain( e, name, value ); } } return temp; } else if ( e.equals("list") ) { org.griphyn.vdl.classes.List list = new org.griphyn.vdl.classes.List(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); this.complain( e, name, value ); } return list; } // unknown return null; // // M // case 'm': if ( e.equals("meta") ) { Logging.instance().log("app", 2, "entering meta mode" ); return new Meta(); } // unknown return null; // // P // case 'p': if ( e.equals("profile") ) { Profile prof = new Profile(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("key") ) { this.log( e, name, value ); prof.setKey(value); } else if ( name.equals("namespace") ) { this.log( e, name, value ); prof.setNamespace(value); } else { this.complain( e, name, value ); } } return prof; } else if ( e.equals("pass") ) { Pass pass = new Pass(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("bind") ){ this.log( e, name, value ); pass.setBind(value); } else { this.complain( e, name, value ); } } return pass; } // unknown return null; // // S // case 's': if ( e.equals("scalar") ) { Scalar scalar = new Scalar(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); this.complain( e, name, value ); } return scalar; } // unknown return null; // // T // case 't': if ( e.equals("text") ) { return new Text(); } else if ( e.equals("transformation") ) { Transformation trans = new Transformation(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("version") ) { this.log( e, name, value ); trans.setVersion(value); } else if ( name.equals("name") ) { this.log( e, name, value ); trans.setName(value); } else if ( name.equals("namespace") ) { this.log( e, name, value ); trans.setNamespace(value); } else if ( name.equals("description") ) { this.log( e, name, value ); trans.setDescription(value); } else if ( name.equals("keyword") ) { this.log( e, name, value ); trans.setKeyword(value); } else if ( name.equals("title") ) { this.log( e, name, value ); trans.setTitle(value); } else if ( name.equals("url") ) { this.log( e, name, value ); trans.setUrl(value); } else if ( name.equals("version") ) { this.log( e, name, value ); trans.setVersion(value); } else if ( name.equals("argumentSeparator") ) { this.log( e, name, value ); trans.setArgumentSeparator(value); } else { this.complain( e, name, value ); } } return trans; } // unknown return null; // // U // case 'u': if ( e.equals("use") ) { Use use = new Use(); for ( int i=0; i<names.size(); ++i ) { String name = (String) names.get(i); String value = (String) values.get(i); if ( name.equals("name") ) { this.log( e, name, value ); use.setName(value); } else if ( name.equals("prefix") ) { this.log( e, name, value ); use.setPrefix(value); } else if ( name.equals("separator") ) { this.log( e, name, value ); use.setSeparator(value); } else if ( name.equals("suffix") ) { this.log( e, name, value ); use.setSuffix(value); } else if ( name.equals("link") ) { this.log( e, name, value ); use.setLink( VDLType.getLinkType(value) ); } else { this.complain( e, name, value ); } } return use; } // unknown return null; default: // FIXME: shouldn't this be an exception? Logging.instance().log( "filler", 0, "Error: No rules defined for element " + e ); return null; } } /** * This method sets the relations between the currently finished XML * element and its containing element in terms of Java objects. * Usually it involves adding the object to the parent's child object * list. * * @param initial is the first charactor of the parent element name * @param parent is a reference to the parent's Java object * @param child is the completed child object to connect to the parent * @return true if the element was added successfully, false, if the * child does not match into the parent. */ protected boolean setElementRelation( char initial, VDL parent, VDL child ) { switch ( initial ) { // // A // case 'a': if ( parent instanceof Argument && child instanceof Leaf ) { // addLeaf is self-checking ((Argument) parent).addLeaf((Leaf) child); return true; } return false; // // C // case 'c': if ( parent instanceof Call ) { Call c = (Call) parent; if ( child instanceof Pass ) { ((Call) parent).addPass((Pass) child); return true; } else if ( child instanceof Meta ) { // dunno return true; } } return false; // // D // case 'd': if ( parent instanceof Declare && child instanceof Value ) { // setValue is self-checking ((Declare) parent).setValue((Value) child); return true; } else if ( parent instanceof Definitions && child instanceof Definition ) { // again, self-checking ((Definitions) parent).addDefinition((Definition) child); // This means we have finished parsing a TR or DV. Invoke callback! if ( m_callback != null ) { // Logging.instance().log( "app", 3, "invoking callback for " + ((Definition) child).shortID() ); m_callback.store((Definition) child); } // FIXME: decrease memory footprint! ((Definitions) parent).removeAllDefinition(); return true; } else if ( parent instanceof Derivation ) { Derivation d = (Derivation) parent; if ( child instanceof Pass ) { d.addPass((Pass) child); return true; } else if ( child instanceof Meta ) { // dunno return true; } } // unknown return false; // // L // case 'l': if ( parent instanceof org.griphyn.vdl.classes.List && child instanceof Scalar ) { ((org.griphyn.vdl.classes.List) parent).addScalar((Scalar) child); return true; } else if ( parent instanceof Local && child instanceof Value ) { // setValue is self-checking ((Local) parent).setValue((Value) child); return true; } // LFN is a *leaf* !! // unknown return false; // // M // case 'm': if ( parent instanceof Meta ) { // dunno whatta do return true; } // unknown return false; // // P // case 'p': if ( parent instanceof Profile && child instanceof Leaf ) { ((Profile) parent).addLeaf((Leaf) child); return true; } else if ( parent instanceof Pass && child instanceof Value ) { ((Pass) parent).setValue((Value) child); return true; } // unknown return false; // // S // case 's': if ( parent instanceof Scalar && child instanceof Leaf ) { ((Scalar) parent).addLeaf((Leaf) child); return true; } // unknown return false; // // T // case 't': if ( parent instanceof Transformation ) { Transformation trans = (Transformation) parent; if ( child instanceof Declare ) { trans.addDeclare((Declare) child); return true; } else if ( child instanceof Argument ) { trans.addArgument((Argument) child); return true; } else if ( child instanceof Call ) { trans.addCall((Call) child); return true; } else if ( child instanceof Local ) { trans.addLocal((Local) child); return true; } else if ( child instanceof Profile ) { trans.addProfile((Profile) child); return true; } else if ( child instanceof Meta ) { // dunno return true; } } // Text is a *leaf* !! return false; // // U // case 'u': // Use is a *leaf* !! return false; default: // FIXME: shouldn't this be an exception? Logging.instance().log( "filler", 0, "Error: unable to join child to parent" ); return false; } } }