Coverage report

  %line %branch
org.apache.commons.jelly.impl.TagScript
63% 
86% 

 1  
 /*
 2  
  * Copyright 2002,2004 The Apache Software Foundation.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.apache.commons.jelly.impl;
 17  
 
 18  
 import java.lang.reflect.InvocationTargetException;
 19  
 import java.util.Hashtable;
 20  
 import java.util.Iterator;
 21  
 import java.util.Map;
 22  
 
 23  
 import org.apache.commons.beanutils.ConvertingWrapDynaBean;
 24  
 import org.apache.commons.beanutils.ConvertUtils;
 25  
 import org.apache.commons.beanutils.DynaBean;
 26  
 import org.apache.commons.beanutils.DynaProperty;
 27  
 
 28  
 import org.apache.commons.jelly.CompilableTag;
 29  
 import org.apache.commons.jelly.JellyContext;
 30  
 import org.apache.commons.jelly.JellyException;
 31  
 import org.apache.commons.jelly.JellyTagException;
 32  
 import org.apache.commons.jelly.DynaTag;
 33  
 import org.apache.commons.jelly.LocationAware;
 34  
 import org.apache.commons.jelly.NamespaceAwareTag;
 35  
 import org.apache.commons.jelly.Script;
 36  
 import org.apache.commons.jelly.Tag;
 37  
 import org.apache.commons.jelly.XMLOutput;
 38  
 import org.apache.commons.jelly.expression.Expression;
 39  
 
 40  
 import org.apache.commons.logging.Log;
 41  
 import org.apache.commons.logging.LogFactory;
 42  
 
 43  
 import org.xml.sax.Attributes;
 44  
 import org.xml.sax.Locator;
 45  
 import org.xml.sax.SAXException;
 46  
 
 47  
 /**
 48  
  * <p><code>TagScript</code> is a Script that evaluates a custom tag.</p>
 49  
  *
 50  
  * <b>Note</b> that this class should be re-entrant and used
 51  
  * concurrently by multiple threads.
 52  
  *
 53  
  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
 54  
  * @version $Revision: 1.43 $
 55  
  */
 56  102
 public class TagScript implements Script {
 57  
 
 58  
     /** The Log to which logging calls will be made. */
 59  38
     private static final Log log = LogFactory.getLog(TagScript.class);
 60  
 
 61  
     /**
 62  
      * Thread local storage for the tag used by the current thread.
 63  
      * This allows us to pool tag instances, per thread to reduce object construction
 64  
      * over head, if we need it.
 65  
      *
 66  
      * Note that we could use the stack and create a new tag for each invocation
 67  
      * if we made a slight change to the Script API to pass in the parent tag.
 68  
      */
 69  2942
     private ThreadLocal tagHolder = new ThreadLocal();
 70  
 
 71  
     /** The attribute expressions that are created */
 72  2942
     protected Map attributes = new Hashtable();
 73  
 
 74  
     /** the optional namespaces Map of prefix -> URI of this single Tag */
 75  
     private Map tagNamespacesMap;
 76  
 
 77  
     /**
 78  
      * The optional namespace context mapping all prefixes -> URIs in scope
 79  
      * at the point this tag is used.
 80  
      * This Map is only created lazily if it is required by the NamespaceAwareTag.
 81  
      */
 82  
     private Map namespaceContext;
 83  
 
 84  
     /** the Jelly file which caused the problem */
 85  
     private String fileName;
 86  
 
 87  
     /** the qualified element name which caused the problem */
 88  
     private String elementName;
 89  
 
 90  
     /** the local (non-namespaced) tag name */
 91  
     private String localName;
 92  
 
 93  
     /** the line number of the tag */
 94  2942
     private int lineNumber = -1;
 95  
 
 96  
     /** the column number of the tag */
 97  2942
     private int columnNumber = -1;
 98  
 
 99  
     /** the factory of Tag instances */
 100  
     private TagFactory tagFactory;
 101  
 
 102  
     /** the body script used for this tag */
 103  
     private Script tagBody;
 104  
 
 105  
     /** the parent TagScript */
 106  
     private TagScript parent;
 107  
 
 108  
     /** the SAX attributes */
 109  
     private Attributes saxAttributes;
 110  
 
 111  
     /**
 112  
      * @return a new TagScript based on whether
 113  
      * the given Tag class is a bean tag or DynaTag
 114  
      */
 115  
     public static TagScript newInstance(Class tagClass) {
 116  2902
         TagFactory factory = new DefaultTagFactory(tagClass);
 117  2902
         return new TagScript(factory);
 118  
     }
 119  
 
 120  0
     public TagScript() {
 121  0
     }
 122  
 
 123  2942
     public TagScript(TagFactory tagFactory) {
 124  2942
         this.tagFactory = tagFactory;
 125  2942
     }
 126  
 
 127  
     public String toString() {
 128  2
         return super.toString() + "[tag=" + elementName + ";at=" + lineNumber + ":" + columnNumber + "]";
 129  
     }
 130  
 
 131  
     /**
 132  
      * Compiles the tags body
 133  
      */
 134  
     public Script compile() throws JellyException {
 135  2932
         if (tagBody != null) {
 136  2932
             tagBody = tagBody.compile();
 137  
         }
 138  2932
         return this;
 139  
     }
 140  
 
 141  
     /**
 142  
      * Sets the optional namespaces prefix -> URI map of
 143  
      * the namespaces attached to this Tag
 144  
      */
 145  
     public void setTagNamespacesMap(Map tagNamespacesMap) {
 146  
         // lets check that this is a thread-safe map
 147  144
         if ( ! (tagNamespacesMap instanceof Hashtable) ) {
 148  144
             tagNamespacesMap = new Hashtable( tagNamespacesMap );
 149  
         }
 150  144
         this.tagNamespacesMap = tagNamespacesMap;
 151  144
     }
 152  
 
 153  
     /**
 154  
      * Configures this TagScript from the SAX Locator, setting the column
 155  
      * and line numbers
 156  
      */
 157  
     public void setLocator(Locator locator) {
 158  2942
         setLineNumber( locator.getLineNumber() );
 159  2942
         setColumnNumber( locator.getColumnNumber() );
 160  2942
     }
 161  
 
 162  
 
 163  
     /** Add an initialization attribute for the tag.
 164  
      * This method must be called after the setTag() method
 165  
      */
 166  
     public void addAttribute(String name, Expression expression) {
 167  3864
         if (log.isDebugEnabled()) {
 168  0
             log.debug("adding attribute name: " + name + " expression: " + expression);
 169  
         }
 170  3864
         attributes.put(name, expression);
 171  3864
     }
 172  
 
 173  
     // Script interface
 174  
     //-------------------------------------------------------------------------
 175  
 
 176  
     /** Evaluates the body of a tag */
 177  
     public void run(JellyContext context, XMLOutput output) throws JellyTagException {
 178  1730
         if ( ! context.isCacheTags() ) {
 179  1730
             clearTag();
 180  
         }
 181  
         try {
 182  1730
             Tag tag = getTag();
 183  1730
             if ( tag == null ) {
 184  0
                 return;
 185  
             }
 186  1730
             tag.setContext(context);
 187  
 
 188  1730
             if ( tag instanceof DynaTag ) {
 189  18
                 DynaTag dynaTag = (DynaTag) tag;
 190  
 
 191  
                 // ### probably compiling this to 2 arrays might be quicker and smaller
 192  104
                 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
 193  68
                     Map.Entry entry = (Map.Entry) iter.next();
 194  68
                     String name = (String) entry.getKey();
 195  68
                     Expression expression = (Expression) entry.getValue();
 196  
 
 197  68
                     Class type = dynaTag.getAttributeType(name);
 198  68
                     Object value = null;
 199  68
                     if (type != null && type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
 200  0
                         value = expression;
 201  
                     }
 202  
                     else {
 203  68
                         value = expression.evaluateRecurse(context);
 204  
                     }
 205  68
                     dynaTag.setAttribute(name, value);
 206  
                 }
 207  
             }
 208  
             else {
 209  
                 // treat the tag as a bean
 210  1712
                 DynaBean dynaBean = new ConvertingWrapDynaBean( tag );
 211  5698
                 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
 212  2274
                     Map.Entry entry = (Map.Entry) iter.next();
 213  2274
                     String name = (String) entry.getKey();
 214  2274
                     Expression expression = (Expression) entry.getValue();
 215  
 
 216  2274
                     DynaProperty property = dynaBean.getDynaClass().getDynaProperty(name);
 217  2274
                     if (property == null) {
 218  0
                         throw new JellyException("This tag does not understand the '" + name + "' attribute" );
 219  
                     }
 220  2274
                     Class type = property.getType();
 221  
 
 222  2274
                     Object value = null;
 223  2274
                     if (type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
 224  1258
                         value = expression;
 225  
                     }
 226  
                     else {
 227  1016
                         value = expression.evaluateRecurse(context);
 228  
                     }
 229  2274
                     dynaBean.set(name, value);
 230  
                 }
 231  
             }
 232  
 
 233  1730
             tag.doTag(output);
 234  1632
         }
 235  
         catch (JellyTagException e) {
 236  98
             handleException(e);
 237  0
         }
 238  
         catch (JellyException e) {
 239  0
             handleException(e);
 240  0
         }
 241  
         catch (RuntimeException e) {
 242  0
             handleException(e);
 243  0
         }
 244  
         catch (Error e) {
 245  
            /*
 246  
             * Not sure if we should be converting errors to exceptions,
 247  
             * but not trivial to remove because JUnit tags throw
 248  
             * Errors in the normal course of operation.  Hmm...
 249  
             */
 250  0
             handleException(e);
 251  
         }
 252  
 
 253  1632
     }
 254  
 
 255  
 
 256  
     // Properties
 257  
     //-------------------------------------------------------------------------
 258  
 
 259  
     /**
 260  
      * @return the tag to be evaluated, creating it lazily if required.
 261  
      */
 262  
     public Tag getTag() throws JellyException {
 263  4754
         Tag tag = (Tag) tagHolder.get();
 264  4754
         if ( tag == null ) {
 265  1756
             tag = createTag();
 266  1756
             if ( tag != null ) {
 267  1756
                 tagHolder.set(tag);
 268  
             }
 269  
         }
 270  4754
         configureTag(tag);
 271  4754
         return tag;
 272  
     }
 273  
 
 274  
     /**
 275  
      * Returns the Factory of Tag instances.
 276  
      * @return the factory
 277  
      */
 278  
     public TagFactory getTagFactory() {
 279  0
         return tagFactory;
 280  
     }
 281  
 
 282  
     /**
 283  
      * Sets the Factory of Tag instances.
 284  
      * @param tagFactory The factory to set
 285  
      */
 286  
     public void setTagFactory(TagFactory tagFactory) {
 287  0
         this.tagFactory = tagFactory;
 288  0
     }
 289  
 
 290  
     /**
 291  
      * Returns the parent.
 292  
      * @return TagScript
 293  
      */
 294  
     public TagScript getParent() {
 295  0
         return parent;
 296  
     }
 297  
 
 298  
     /**
 299  
      * Returns the tagBody.
 300  
      * @return Script
 301  
      */
 302  
     public Script getTagBody() {
 303  0
         return tagBody;
 304  
     }
 305  
 
 306  
     /**
 307  
      * Sets the parent.
 308  
      * @param parent The parent to set
 309  
      */
 310  
     public void setParent(TagScript parent) {
 311  2942
         this.parent = parent;
 312  2942
     }
 313  
 
 314  
     /**
 315  
      * Sets the tagBody.
 316  
      * @param tagBody The tagBody to set
 317  
      */
 318  
     public void setTagBody(Script tagBody) {
 319  2942
         this.tagBody = tagBody;
 320  2942
     }
 321  
 
 322  
     /**
 323  
      * @return the Jelly file which caused the problem
 324  
      */
 325  
     public String getFileName() {
 326  0
         return fileName;
 327  
     }
 328  
 
 329  
     /**
 330  
      * Sets the Jelly file which caused the problem
 331  
      */
 332  
     public void setFileName(String fileName) {
 333  2942
         this.fileName = fileName;
 334  2942
     }
 335  
 
 336  
 
 337  
     /**
 338  
      * @return the element name which caused the problem
 339  
      */
 340  
     public String getElementName() {
 341  0
         return elementName;
 342  
     }
 343  
 
 344  
     /**
 345  
      * Sets the element name which caused the problem
 346  
      */
 347  
     public void setElementName(String elementName) {
 348  2942
         this.elementName = elementName;
 349  2942
     }
 350  
     /**
 351  
      * @return the line number of the tag
 352  
      */
 353  
     public int getLineNumber() {
 354  0
         return lineNumber;
 355  
     }
 356  
 
 357  
     /**
 358  
      * Sets the line number of the tag
 359  
      */
 360  
     public void setLineNumber(int lineNumber) {
 361  2942
         this.lineNumber = lineNumber;
 362  2942
     }
 363  
 
 364  
     /**
 365  
      * @return the column number of the tag
 366  
      */
 367  
     public int getColumnNumber() {
 368  0
         return columnNumber;
 369  
     }
 370  
 
 371  
     /**
 372  
      * Sets the column number of the tag
 373  
      */
 374  
     public void setColumnNumber(int columnNumber) {
 375  2942
         this.columnNumber = columnNumber;
 376  2942
     }
 377  
 
 378  
     /**
 379  
      * Returns the SAX attributes of this tag
 380  
      * @return Attributes
 381  
      */
 382  
     public Attributes getSaxAttributes() {
 383  1768
         return saxAttributes;
 384  
     }
 385  
 
 386  
     /**
 387  
      * Sets the SAX attributes of this tag
 388  
      * @param saxAttributes The saxAttributes to set
 389  
      */
 390  
     public void setSaxAttributes(Attributes saxAttributes) {
 391  2902
         this.saxAttributes = saxAttributes;
 392  2902
     }
 393  
 
 394  
     /**
 395  
      * Returns the local, non namespaced XML name of this tag
 396  
      * @return String
 397  
      */
 398  
     public String getLocalName() {
 399  0
         return localName;
 400  
     }
 401  
 
 402  
     /**
 403  
      * Sets the local, non namespaced name of this tag.
 404  
      * @param localName The localName to set
 405  
      */
 406  
     public void setLocalName(String localName) {
 407  2942
         this.localName = localName;
 408  2942
     }
 409  
 
 410  
 
 411  
     /**
 412  
      * Returns the namespace context of this tag. This is all the prefixes
 413  
      * in scope in the document where this tag is used which are mapped to
 414  
      * their namespace URIs.
 415  
      *
 416  
      * @return a Map with the keys are namespace prefixes and the values are
 417  
      * namespace URIs.
 418  
      */
 419  
     public synchronized Map getNamespaceContext() {
 420  0
         if (namespaceContext == null) {
 421  0
             if (parent != null) {
 422  0
                 namespaceContext = getParent().getNamespaceContext();
 423  0
                 if (tagNamespacesMap != null && !tagNamespacesMap.isEmpty()) {
 424  
                     // create a new child context
 425  0
                     Hashtable newContext = new Hashtable(namespaceContext.size()+1);
 426  0
                     newContext.putAll(namespaceContext);
 427  0
                     newContext.putAll(tagNamespacesMap);
 428  0
                     namespaceContext = newContext;
 429  
                 }
 430  
             }
 431  
             else {
 432  0
                 namespaceContext = tagNamespacesMap;
 433  0
                 if (namespaceContext == null) {
 434  0
                     namespaceContext = new Hashtable();
 435  
                 }
 436  
             }
 437  
         }
 438  0
         return namespaceContext;
 439  
     }
 440  
 
 441  
     // Implementation methods
 442  
     //-------------------------------------------------------------------------
 443  
 
 444  
     /**
 445  
      * Factory method to create a new Tag instance.
 446  
      * The default implementation is to delegate to the TagFactory
 447  
      */
 448  
     protected Tag createTag() throws JellyException {
 449  1756
         if ( tagFactory != null) {
 450  1756
             return tagFactory.createTag(localName, getSaxAttributes());
 451  
         }
 452  0
         return null;
 453  
     }
 454  
 
 455  
 
 456  
     /**
 457  
      * Compiles a newly created tag if required, sets its parent and body.
 458  
      */
 459  
     protected void configureTag(Tag tag) throws JellyException {
 460  4754
         if (tag instanceof CompilableTag) {
 461  0
             ((CompilableTag) tag).compile();
 462  
         }
 463  4754
         Tag parentTag = null;
 464  4754
         if ( parent != null ) {
 465  2998
             parentTag = parent.getTag();
 466  
         }
 467  4754
         tag.setParent( parentTag );
 468  4754
         tag.setBody( tagBody );
 469  
 
 470  4754
         if (tag instanceof NamespaceAwareTag) {
 471  0
             NamespaceAwareTag naTag = (NamespaceAwareTag) tag;
 472  0
             naTag.setNamespaceContext(getNamespaceContext());
 473  
         }
 474  4754
         if (tag instanceof LocationAware) {
 475  0
             applyLocation((LocationAware) tag);
 476  
         }
 477  4754
     }
 478  
 
 479  
     /**
 480  
      * Flushes the current cached tag so that it will be created, lazily, next invocation
 481  
      */
 482  
     protected void clearTag() {
 483  1730
         tagHolder.set(null);
 484  1730
     }
 485  
 
 486  
     /**
 487  
      * Allows the script to set the tag instance to be used, such as in a StaticTagScript
 488  
      * when a StaticTag is switched with a DynamicTag
 489  
      */
 490  
     protected void setTag(Tag tag) {
 491  26
         tagHolder.set(tag);
 492  26
     }
 493  
 
 494  
     /**
 495  
      * Output the new namespace prefixes used for this element
 496  
      */
 497  
     protected void startNamespacePrefixes(XMLOutput output) throws SAXException {
 498  26
         if ( tagNamespacesMap != null ) {
 499  6
             for ( Iterator iter = tagNamespacesMap.entrySet().iterator(); iter.hasNext(); ) {
 500  2
                 Map.Entry entry = (Map.Entry) iter.next();
 501  2
                 String prefix = (String) entry.getKey();
 502  2
                 String uri = (String) entry.getValue();
 503  2
                 output.startPrefixMapping(prefix, uri);
 504  
             }
 505  
         }
 506  26
     }
 507  
 
 508  
     /**
 509  
      * End the new namespace prefixes mapped for the current element
 510  
      */
 511  
     protected void endNamespacePrefixes(XMLOutput output) throws SAXException {
 512  26
         if ( tagNamespacesMap != null ) {
 513  6
             for ( Iterator iter = tagNamespacesMap.keySet().iterator(); iter.hasNext(); ) {
 514  2
                 String prefix = (String) iter.next();
 515  2
                 output.endPrefixMapping(prefix);
 516  
             }
 517  
         }
 518  26
     }
 519  
 
 520  
     /**
 521  
      * Converts the given value to the required type.
 522  
      *
 523  
      * @param value is the value to be converted. This will not be null
 524  
      * @param requiredType the type that the value should be converted to
 525  
      */
 526  
     protected Object convertType(Object value, Class requiredType)
 527  
         throws JellyException {
 528  0
         if (requiredType.isInstance(value)) {
 529  0
             return value;
 530  
         }
 531  0
         if (value instanceof String) {
 532  0
             return ConvertUtils.convert((String) value, requiredType);
 533  
         }
 534  0
         return value;
 535  
     }
 536  
 
 537  
     /**
 538  
      * Creates a new Jelly exception, adorning it with location information
 539  
      */
 540  
     protected JellyException createJellyException(String reason) {
 541  0
         return new JellyException(
 542  
             reason, fileName, elementName, columnNumber, lineNumber
 543  
         );
 544  
     }
 545  
 
 546  
     /**
 547  
      * Creates a new Jelly exception, adorning it with location information
 548  
      */
 549  
     protected JellyException createJellyException(String reason, Exception cause) {
 550  0
         if (cause instanceof JellyException) {
 551  0
             return (JellyException) cause;
 552  
         }
 553  
 
 554  0
         if (cause instanceof InvocationTargetException) {
 555  0
             return new JellyException(
 556  
                 reason,
 557  
                 ((InvocationTargetException) cause).getTargetException(),
 558  
                 fileName,
 559  
                 elementName,
 560  
                 columnNumber,
 561  
                 lineNumber);
 562  
         }
 563  0
         return new JellyException(
 564  
             reason, cause, fileName, elementName, columnNumber, lineNumber
 565  
         );
 566  
     }
 567  
 
 568  
     /**
 569  
      * A helper method to handle this Jelly exception.
 570  
      * This method adorns the JellyException with location information
 571  
      * such as adding line number information etc.
 572  
      */
 573  
     protected void handleException(JellyTagException e) throws JellyTagException {
 574  98
         if (log.isTraceEnabled()) {
 575  0
             log.trace( "Caught exception: " + e, e );
 576  
         }
 577  
 
 578  98
         applyLocation(e);
 579  
 
 580  98
         throw e;
 581  
     }
 582  
 
 583  
     /**
 584  
      * A helper method to handle this Jelly exception.
 585  
      * This method adorns the JellyException with location information
 586  
      * such as adding line number information etc.
 587  
      */
 588  
     protected void handleException(JellyException e) throws JellyTagException {
 589  0
         if (log.isTraceEnabled()) {
 590  0
             log.trace( "Caught exception: " + e, e );
 591  
         }
 592  
 
 593  0
         applyLocation(e);
 594  
 
 595  0
         throw new JellyTagException(e);
 596  
     }
 597  
 
 598  
     protected void applyLocation(LocationAware locationAware) {
 599  98
         if (locationAware.getLineNumber() == -1) {
 600  52
             locationAware.setColumnNumber(columnNumber);
 601  52
             locationAware.setLineNumber(lineNumber);
 602  
         }
 603  98
         if ( locationAware.getFileName() == null ) {
 604  52
             locationAware.setFileName( fileName );
 605  
         }
 606  98
         if ( locationAware.getElementName() == null ) {
 607  52
             locationAware.setElementName( elementName );
 608  
         }
 609  98
     }
 610  
 
 611  
     /**
 612  
      * A helper method to handle this non-Jelly exception.
 613  
      * This method will rethrow the exception, wrapped in a JellyException
 614  
      * while adding line number information etc.
 615  
      */
 616  
     protected void handleException(Exception e) throws JellyTagException {
 617  0
         if (log.isTraceEnabled()) {
 618  0
             log.trace( "Caught exception: " + e, e );
 619  
         }
 620  
 
 621  0
         if (e instanceof LocationAware) {
 622  0
             applyLocation((LocationAware) e);
 623  
         }
 624  
 
 625  0
         if ( e instanceof JellyException ) {
 626  0
             e.fillInStackTrace();
 627  
         }
 628  
 
 629  0
         if ( e instanceof InvocationTargetException) {
 630  0
             throw new JellyTagException( ((InvocationTargetException)e).getTargetException(),
 631  
                                       fileName,
 632  
                                       elementName,
 633  
                                       columnNumber,
 634  
                                       lineNumber );
 635  
         }
 636  
 
 637  0
         throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
 638  
     }
 639  
 
 640  
     /**
 641  
      * A helper method to handle this non-Jelly exception.
 642  
      * This method will rethrow the exception, wrapped in a JellyException
 643  
      * while adding line number information etc.
 644  
      *
 645  
      * Is this method wise?
 646  
      */
 647  
     protected void handleException(Error e) throws Error, JellyTagException {
 648  0
         if (log.isTraceEnabled()) {
 649  0
             log.trace( "Caught exception: " + e, e );
 650  
         }
 651  
 
 652  0
         if (e instanceof LocationAware) {
 653  0
             applyLocation((LocationAware) e);
 654  
         }
 655  
 
 656  0
         throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
 657  
     }
 658  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.