| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * $Id$ |
| */ |
| |
| /* |
| * |
| * TransformStateTestlet.java |
| * |
| */ |
| package org.apache.qetest.xalanj2; |
| |
| import java.util.Hashtable; |
| |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.sax.SAXResult; |
| import javax.xml.transform.stream.StreamSource; |
| |
| import org.apache.qetest.Datalet; |
| import org.apache.qetest.Logger; |
| import org.apache.qetest.LoggingHandler; |
| import org.apache.qetest.QetestUtils; |
| import org.apache.qetest.TestletImpl; |
| import org.apache.qetest.XMLFileLogger; |
| import org.apache.xalan.templates.ElemTemplate; |
| import org.apache.xalan.templates.ElemTemplateElement; |
| import org.apache.xalan.transformer.TransformState; |
| import org.apache.xalan.transformer.TransformerClient; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.traversal.NodeIterator; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Testlet for testing TransformState of a stylesheet. |
| * |
| * In progress - data-driven tests for tooling API's. |
| * Currently uses cheap-o validation method |
| * |
| * @author Shane_Curcuru@lotus.com |
| * @version $Id$ |
| */ |
| public class TransformStateTestlet extends TestletImpl |
| implements ContentHandler, TransformerClient |
| |
| { |
| // Initialize our classname for TestletImpl's main() method |
| static { thisClassName = "org.apache.qetest.xsl.TransformStateTestlet"; } |
| |
| // Initialize our defaultDatalet |
| { defaultDatalet = (Datalet)new TransformStateDatalet(); } |
| |
| /** |
| * Class-wide copy of TransformStateDatalet. |
| * This is used in execute() and in various worker methods |
| * underneath the ContentHandler interface. |
| */ |
| protected TransformStateDatalet tsDatalet = null; |
| |
| /** |
| * Accesor method for a brief description of this test. |
| * |
| * @return String describing what this TransformStateTestlet does. |
| */ |
| public String getDescription() |
| { |
| return "TransformStateTestlet"; |
| } |
| |
| |
| /** |
| * Run this TransformStateTestlet: execute it's test and return. |
| * |
| * @param Datalet to use as data point for the test. |
| */ |
| public void execute(Datalet d) |
| { |
| try |
| { |
| tsDatalet = (TransformStateDatalet)d; |
| } |
| catch (ClassCastException e) |
| { |
| logger.checkErr("Datalet provided is not a TransformStateDatalet; cannot continue with " + d); |
| return; |
| } |
| |
| logger.logMsg(Logger.STATUSMSG, "About to test: " |
| + (null == tsDatalet.inputName |
| ? tsDatalet.xmlName |
| : tsDatalet.inputName)); |
| try |
| { |
| // Perform the transform |
| TransformerFactory factory = TransformerFactory.newInstance(); |
| logger.logMsg(Logger.TRACEMSG, "---- About to newTransformer " + QetestUtils.filenameToURL(tsDatalet.inputName)); |
| Transformer transformer = factory.newTransformer(new StreamSource(QetestUtils.filenameToURL(tsDatalet.inputName))); |
| logger.logMsg(Logger.TRACEMSG, "---- About to transform " + QetestUtils.filenameToURL(tsDatalet.xmlName) + " into: SAXResult(this-no disk output)"); |
| |
| // Note most validation happens here: we get ContentHandler |
| // callbacks from being in the SAXResult, and that's where |
| // we do our validation |
| transformer.transform(new StreamSource(QetestUtils.filenameToURL(tsDatalet.xmlName)), |
| new SAXResult(this)); // use us to handle result |
| |
| logger.logMsg(Logger.INFOMSG, "---- Afterwards, this.transformState=" + transformState); |
| } |
| catch (Throwable t) |
| { |
| // Put the logThrowable first, so it appears before |
| // the Fail record, and gets color-coded |
| logger.logThrowable(Logger.ERRORMSG, t, getDescription() + " " + tsDatalet.getDescription()); |
| logger.checkFail(getDescription() + " " + tsDatalet.getDescription() |
| + " threw: " + t.toString()); |
| return; |
| } |
| } |
| ////////////////// partially Implement LoggingHandler ////////////////// |
| /** Cheap-o string representation of last event we got. */ |
| protected String lastItem = LoggingHandler.NOTHING_HANDLED; |
| |
| |
| /** |
| * Accessor for string representation of last event we got. |
| * @param s string to set |
| */ |
| protected void setLastItem(String s) |
| { |
| lastItem = s; |
| } |
| |
| |
| /** |
| * Accessor for string representation of last event we got. |
| * @return last event string we had |
| */ |
| public String getLast() |
| { |
| return lastItem; |
| } |
| |
| /** |
| * Worker routine to validate a TransformState based on an event/value. |
| * Note: this may not be threadsafe! |
| * //@todo actually add validation code - just logs out now |
| * @param ts TransformState to validate, if null, just logs it |
| * @param event our String constant of START_ELEMENT, etc. |
| * @param value any String value of the current event |
| */ |
| protected void validateTransformState(TransformState ts, String event, String value) |
| { |
| if(null == transformState) |
| { |
| // We should never have a null TransformState since the |
| // transformer should have always filled it in |
| logger.checkErr("validateTransformState(ts-NULL!, " + event + ")=" + value); |
| return; |
| } |
| logTransformStateDump(logger, Logger.INFOMSG, ts, event, value); |
| |
| // Cheap-o validation: only validate items on column 99 |
| if (99 == ts.getCurrentElement().getColumnNumber()) |
| { |
| int line = ts.getCurrentElement().getLineNumber(); |
| // Get cheap-o validation from the datalet for this line.. |
| String exp = (String)tsDatalet.validate99.get(line + ".current.name"); |
| // .. If there's an expected value for this line's property.. |
| if (null != exp) |
| // .. Then check if it's equal and report pass/fail |
| checkString(ts.getCurrentTemplate().getName().toString(), exp, |
| "Validate L" + line + "C99 .current.name"); |
| |
| exp = (String)tsDatalet.validate99.get(line + ".current.match"); |
| if (null != exp) |
| checkString(ts.getCurrentTemplate().getMatch().getPatternString(), exp, |
| "Validate L" + line + "C99 .current.match"); |
| |
| exp = (String)tsDatalet.validate99.get(line + ".current.mode"); |
| if (null != exp) |
| checkString(ts.getCurrentTemplate().getMode().toString(), exp, |
| "Validate L" + line + "C99 .current.mode"); |
| |
| |
| exp = (String)tsDatalet.validate99.get(line + ".matched.name"); |
| if (null != exp) |
| checkString(ts.getMatchedTemplate().getName().toString(), exp, |
| "Validate L" + line + "C99 .matched.name"); |
| |
| exp = (String)tsDatalet.validate99.get(line + ".matched.match"); |
| if (null != exp) |
| checkString(ts.getMatchedTemplate().getMatch().getPatternString(), exp, |
| "Validate L" + line + "C99 .matched.match"); |
| |
| exp = (String)tsDatalet.validate99.get(line + ".matched.mode"); |
| if (null != exp) |
| checkString(ts.getMatchedTemplate().getMode().toString(), exp, |
| "Validate L" + line + "C99 .matched.mode"); |
| } |
| |
| /*********************************************** |
| // Comment out validation using ExpectedObjects since they hang 28-Jun-01 -sc |
| // See if we have a matching expected state for this event |
| //@todo use event string as part of hashkey!!! |
| String marker = ExpectedTransformState.getHashKey(ts); |
| // Add on the event as well; see ExpectedTransformState.getHashKey() |
| // for why we have to do this separately |
| marker += ExpectedTransformState.SEP + event; |
| ExpectedTransformState ets = (ExpectedTransformState)tsDatalet.expectedTransformStates.get(marker); |
| logger.logMsg(Logger.TRACEMSG, "ETS-HACK:" + marker + "=" + ets); |
| if (null != ets) |
| { |
| // Ask it to validate itself as needed |
| synchronized(ets) // voodoo: attempt to solve hang problems |
| { |
| ExpectedObjectCheckService.check(logger, ts, ets, "Compare ExpectedTransformState of " + marker); |
| } |
| } |
| // Comment out validation using ExpectedObjects since they hang 28-Jun-01 -sc |
| ***********************************************/ |
| } |
| |
| private void checkString(String act, String exp, String comment) |
| { |
| if (exp.equals(act)) |
| logger.checkPass(comment); |
| else |
| logger.checkFail(comment + "; act(" + act + ") exp(" + exp + ")"); |
| } |
| |
| ////////////////// Utility methods for TransformState ////////////////// |
| /** |
| * Utility method to dump data from TransformState. |
| * @return String describing various bits of the state |
| */ |
| protected void logTransformStateDump(Logger logger, int traceLoggingLevel, |
| TransformState ts, String event, String value) |
| { |
| String elemName = "transformStateDump"; |
| Hashtable attrs = new Hashtable(); |
| attrs.put("event", event); |
| if (null != value) |
| attrs.put("value", value); |
| attrs.put("location", "L" + ts.getCurrentElement().getLineNumber() |
| + "C" + ts.getCurrentElement().getColumnNumber()); |
| |
| StringBuffer buf = new StringBuffer(); |
| ElemTemplateElement elem = ts.getCurrentElement(); // may be actual or default template |
| buf.append(" <currentElement>" |
| + XMLFileLogger.escapeString(XalanDumper.dump(elem, XalanDumper.DUMP_DEFAULT)) + "</currentElement>"); |
| |
| ElemTemplate currentTempl = ts.getCurrentTemplate(); // Actual current template |
| buf.append("\n <currentTemplate>" |
| + XMLFileLogger.escapeString(XalanDumper.dump(currentTempl, XalanDumper.DUMP_DEFAULT)) + "</currentTemplate>"); |
| |
| ElemTemplate matchTempl = ts.getMatchedTemplate(); // Actual matched template |
| if (matchTempl != currentTempl) |
| buf.append("\n <matchedTemplate>" |
| + XMLFileLogger.escapeString(XalanDumper.dump(matchTempl, XalanDumper.DUMP_DEFAULT)) + "</matchedTemplate>"); |
| |
| // Optimization: skip most logging when on endElement |
| if (!END_ELEMENT.equals(event)) |
| { |
| Node n = ts.getCurrentNode(); // current context node in source tree |
| buf.append("\n <currentNode>" |
| + XMLFileLogger.escapeString(XalanDumper.dump(n, XalanDumper.DUMP_DEFAULT)) + "</currentNode>"); |
| |
| Node matchedNode = ts.getMatchedNode(); // node in source matched via getMatchedTemplate |
| // Optimization: only output if different |
| if (n != matchedNode) |
| buf.append("\n <matchedNode>" |
| + XMLFileLogger.escapeString(XalanDumper.dump(matchedNode, XalanDumper.DUMP_DEFAULT)) + "</matchedNode>"); |
| |
| NodeIterator contextNodeList = ts.getContextNodeList(); // current context node list |
| Node rootNode = contextNodeList.getRoot(); |
| // Optimization: only output if different |
| if (n != rootNode) |
| buf.append("\n <contextNodeListGetRoot>" |
| + XMLFileLogger.escapeString(XalanDumper.dump(rootNode, XalanDumper.DUMP_DEFAULT)) + "</contextNodeListGetRoot>"); |
| |
| Transformer transformer = ts.getTransformer(); // current transformer working |
| // Optimization: only dump transformer at startElement to save space |
| if (START_ELEMENT.equals(event)) |
| { |
| buf.append("\n <transformer>" |
| + XMLFileLogger.escapeString(XalanDumper.dump(transformer, XalanDumper.DUMP_DEFAULT)) + "</transformer>"); |
| } |
| else |
| { |
| // Just log error case if transformer is ever null |
| if (null == transformer) { |
| buf.append("\n <transformer>" |
| + "ERROR! Transformer was null!" + "</transformer>"); |
| } |
| } |
| } |
| |
| logger.logElement(traceLoggingLevel, elemName, attrs, buf.toString()); |
| } |
| |
| //----------------------------------------------------------- |
| //---- Implement the TransformerClient interface |
| //----------------------------------------------------------- |
| /** |
| * A TransformState object that we use to log state data. |
| * This is the equivalent of the defaultHandler, even though |
| * that's not really the right metaphor. This class could be |
| * upgraded to have both a default ContentHandler and a |
| * defaultTransformerClient in the future. |
| */ |
| protected TransformState transformState = null; |
| |
| |
| /** |
| * Implement TransformerClient.setTransformState interface. |
| * Pass in a reference to a TransformState object, which |
| * can be used during SAX ContentHandler events to obtain |
| * information about he state of the transformation. This |
| * method will be called before each startDocument event. |
| * |
| * @param ts A reference to a TransformState object |
| */ |
| public void setTransformState(TransformState ts) |
| { |
| transformState = ts; |
| } |
| |
| //----------------------------------------------------------- |
| //---- Implement the ContentHandler interface |
| //----------------------------------------------------------- |
| protected final String START_ELEMENT = "startElement:"; |
| protected final String END_ELEMENT = "endElement:"; |
| protected final String CHARACTERS = "characters:"; |
| |
| // String Locator.getPublicId() null if none available |
| // String Locator.getPublicId() null if none available |
| // int Locator.getLineNumber() -1 if none available |
| // int Locator.getColumnNumber() -1 if none available |
| protected Locator ourLocator = null; |
| |
| /** |
| * Implement ContentHandler.setDocumentLocator. |
| * If available, this should always be called prior to a |
| * startDocument event. |
| */ |
| public void setDocumentLocator (Locator locator) |
| { |
| // Note: this implies this class is !not! threadsafe |
| ourLocator = locator; // future use |
| if (null != locator) |
| setLastItem("setDocumentLocator.getSystemId():" + locator.getSystemId()); |
| else |
| setLastItem("setDocumentLocator:NULL"); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| } |
| |
| |
| /** Cached TransformState object during lifetime startDocument -> endDocument. */ |
| // Note: is this correct? Will it always be the same object? |
| protected TransformState docCachedTransformState = null; |
| /** Implement ContentHandler.startDocument. */ |
| public void startDocument () |
| throws SAXException |
| { |
| setLastItem("startDocument"); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| // Comment out check call since the spec'd functionality |
| // is very likely to change to *not* be in startDocument 19-Jun-01 -sc |
| // logger.check((null != transformState), true, "transformState non-null in startDocument"); |
| logger.logMsg(Logger.STATUSMSG, "transformState in startDocument is: " + transformState); |
| docCachedTransformState = transformState; // see endDocument |
| } |
| |
| |
| /** Implement ContentHandler.endDocument. */ |
| public void endDocument() |
| throws SAXException |
| { |
| setLastItem("endDocument"); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| // Comment out check call since the spec'd functionality |
| // is very likely to change to *not* be in startDocument 19-Jun-01 -sc |
| // logger.checkObject(docCachedTransformState, transformState, |
| // "transformState same in endDocument as startDocument"); // see startDocument |
| logger.logMsg(Logger.STATUSMSG, "transformState in endDocument is: " + transformState); |
| docCachedTransformState = null; |
| } |
| |
| |
| /** Implement ContentHandler.startPrefixMapping. */ |
| public void startPrefixMapping (String prefix, String uri) |
| throws SAXException |
| { |
| setLastItem("startPrefixMapping: " + prefix + ", " + uri); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| } |
| |
| |
| /** Implement ContentHandler.endPrefixMapping. */ |
| public void endPrefixMapping (String prefix) |
| throws SAXException |
| { |
| setLastItem("endPrefixMapping: " + prefix); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| } |
| |
| |
| /** Implement ContentHandler.startElement. */ |
| public void startElement (String namespaceURI, String localName, |
| String qName, Attributes atts) |
| throws SAXException |
| { |
| StringBuffer buf = new StringBuffer(); |
| buf.append(namespaceURI + ", " |
| + localName + ", " + qName + ";"); |
| |
| int n = atts.getLength(); |
| for(int i = 0; i < n; i++) |
| { |
| buf.append(", " + atts.getQName(i)); |
| } |
| setLastItem(START_ELEMENT + buf.toString()); |
| |
| validateTransformState(transformState, START_ELEMENT, buf.toString()); |
| } |
| |
| |
| /** Implement ContentHandler.endElement. */ |
| public void endElement (String namespaceURI, String localName, String qName) |
| throws SAXException |
| { |
| setLastItem(END_ELEMENT + namespaceURI + ", " + localName + ", " + qName); |
| |
| validateTransformState(transformState, END_ELEMENT, null); |
| } |
| |
| |
| /** Implement ContentHandler.characters. */ |
| public void characters (char ch[], int start, int length) |
| throws SAXException |
| { |
| String s = new String(ch, start, length); |
| setLastItem(CHARACTERS + "\"" + s + "\""); |
| |
| validateTransformState(transformState, CHARACTERS, s); |
| } |
| |
| |
| /** Implement ContentHandler.ignorableWhitespace. */ |
| public void ignorableWhitespace (char ch[], int start, int length) |
| throws SAXException |
| { |
| setLastItem("ignorableWhitespace: len " + length); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| } |
| |
| |
| /** Implement ContentHandler.processingInstruction. */ |
| public void processingInstruction (String target, String data) |
| throws SAXException |
| { |
| setLastItem("processingInstruction: " + target + ", " + data); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| } |
| |
| |
| /** Implement ContentHandler.skippedEntity. */ |
| public void skippedEntity (String name) |
| throws SAXException |
| { |
| setLastItem("skippedEntity: " + name); |
| logger.logMsg(Logger.INFOMSG, getLast()); |
| } |
| |
| } // end of class TransformStateTestlet |
| |