001/*
002 * Copyright (c) 2004-2010, Kohsuke Kawaguchi
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
006 * that the following conditions are met:
007 *
008 *     * Redistributions of source code must retain the above copyright notice, this list of
009 *       conditions and the following disclaimer.
010 *     * Redistributions in binary form must reproduce the above copyright notice, this list of
011 *       conditions and the following disclaimer in the documentation and/or other materials
012 *       provided with the distribution.
013 *
014 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
015 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
016 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
017 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
018 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
019 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
020 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
021 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
022 */
023
024package org.kohsuke.stapler.jelly;
025
026import org.apache.commons.jelly.JellyContext;
027import org.apache.commons.jelly.JellyException;
028import org.apache.commons.jelly.JellyTagException;
029import org.apache.commons.jelly.Tag;
030import org.apache.commons.jelly.TagLibrary;
031import org.apache.commons.jelly.XMLOutput;
032import org.apache.commons.jelly.expression.ConstantExpression;
033import org.apache.commons.jelly.expression.Expression;
034import org.apache.commons.jelly.impl.ExpressionAttribute;
035import org.apache.commons.jelly.impl.StaticTag;
036import org.apache.commons.jelly.impl.StaticTagScript;
037import org.apache.commons.jelly.impl.TagScript;
038import org.xml.sax.Attributes;
039import org.xml.sax.SAXException;
040import org.xml.sax.helpers.AttributesImpl;
041
042/**
043 * Jelly tag library for static tags.
044 *
045 * <p>
046 * Unlike {@link StaticTagScript}, this doesn't even try to see if the tag name is available as a dynamic tag.
047 * By not doing so, this implementation achieves a better performance both in speed and memory usage. 
048 *
049 * <p>
050 * Jelly by default uses {@link StaticTagScript} instance to represent a tag that's parsed as a static tag,
051 * and for each invocation, this code checks if the tag it represents is now defined as a dynamic tag.
052 * Plus it got the code to cache {@link StaticTag} instances per thread, which consumes more space and time.
053 *
054 * @author Kohsuke Kawaguchi
055 * @since 1.342
056 */
057public class ReallyStaticTagLibrary extends TagLibrary {
058    /**
059     * IIUC, this method will never be invoked.
060     */
061    @Override
062    public Tag createTag(final String name, Attributes attributes) throws JellyException {
063        return null;
064    }
065
066    @Override
067    public TagScript createTagScript(String tagName, Attributes atts) throws JellyException {
068        return createTagScript();
069    }
070
071    /**
072     * Creates a new instance of {@link TagScript} that generates a literal element.
073     */
074    public static TagScript createTagScript() {
075        return new TagScript() {
076            /**
077             * If all the attributes are constant, as is often the case with literal tags,
078             * then we can skip the attribute expression evaluation altogether.
079             */
080            private boolean allAttributesAreConstant = true;
081
082            @Override
083            public void addAttribute(String name, Expression expression) {
084                allAttributesAreConstant &= expression instanceof ConstantExpression;
085                super.addAttribute(name, expression);
086            }
087
088            @Override
089            public void addAttribute(String name, String prefix, String nsURI, Expression expression) {
090                allAttributesAreConstant &= expression instanceof ConstantExpression;
091                super.addAttribute(name, prefix, nsURI, expression);
092            }
093
094            public void run(JellyContext context, XMLOutput output) throws JellyTagException {
095                Attributes actual = (allAttributesAreConstant && !EMIT_LOCATION) ? getSaxAttributes() : buildAttributes(context);
096
097                try {
098                    output.startElement(getNsUri(),getLocalName(),getElementName(),actual);
099                    getTagBody().run(context,output);
100                    output.endElement(getNsUri(),getLocalName(),getElementName());
101                } catch (SAXException x) {
102                    throw new JellyTagException(x);
103                }
104            }
105
106            private AttributesImpl buildAttributes(JellyContext context) {
107                AttributesImpl actual = new AttributesImpl();
108
109                for (ExpressionAttribute att : attributes.values()) {
110                    Expression expression = att.exp;
111                    String v = expression.evaluateAsString(context);
112                    if (v==null)    continue; // treat null as no attribute
113                    actual.addAttribute(att.nsURI, att.name, att.qname(),"CDATA", v);
114                }
115
116                if (EMIT_LOCATION) {
117                    actual.addAttribute("","file","file","CDATA",String.valueOf(getFileName()));
118                    actual.addAttribute("","line","line","CDATA",String.valueOf(getLineNumber()));
119
120                    // try to obtain the meaningful part of the script and put it in CSS with a
121                    // class name like "jelly-foo-bar-xyz" given "file://path/to/src/tree/src/main/resources/foo/bar/xyz.jelly"
122                    String form = getFileName().replace('\\','/');
123                    for (String suffix : SUFFIX) {
124                        int idx = form.lastIndexOf(suffix);
125                        if (idx>0)  form=form.substring(idx+suffix.length());
126                    }
127
128                    int c = actual.getIndex("class");
129                    if (c>=0)   actual.setValue(c, actual.getValue(c)+" "+form);
130                    else        actual.addAttribute("","class","class","CDATA",form);
131                }
132
133                return actual;
134            }
135        };
136    }
137    /**
138     * Reusable instance.
139     */
140    public static final TagLibrary INSTANCE = new ReallyStaticTagLibrary();
141
142    /**
143     * If true, emit the location information.
144     */
145    public static boolean EMIT_LOCATION = false;
146
147    private static final String[] SUFFIX = {"src/main/resources/","src/test/resources/"};
148}