Package org.apache.cocoon.components.language.markup

Source Code of org.apache.cocoon.components.language.markup.AbstractMarkupLanguage$LanguageDescriptor

/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed 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.
*/
package org.apache.cocoon.components.language.markup;

import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;

import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceResolver;

import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.cocoon.components.language.programming.ProgrammingLanguage;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.excalibur.store.Store;
import org.apache.cocoon.util.HashMap;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
* Base implementation of <code>MarkupLanguage</code>. This class uses
* logicsheets as the only means of code generation. Code generation
* should be decoupled from this context!!!
*
* @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
* @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
* @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
* @version CVS $Id: AbstractMarkupLanguage.java 278591 2005-09-04 13:24:23Z pier $
*/
public abstract class AbstractMarkupLanguage
        extends AbstractLogEnabled
        implements MarkupLanguage, Serviceable, Configurable, Recyclable, Disposable
{
    /**
     * Name "attr-interpolation" of boolean attribute to enable
     * expression interpolation in attribute values.
     */
    public static final String ATTR_INTERPOLATION = "attr-interpolation";

    /**
     * Name "text-interpolation" of boolean attribute to enable
     * expression interpolation inside text nodes.
     */
    public static final String TEXT_INTERPOLATION = "text-interpolation";

    /** The 'file' URL protocol. */
    private static final String FILE = "file:";

    /** Prefix for cache keys to avoid name clash with the XSLTProcessor */
    private static final String CACHE_PREFIX = "logicsheet:";

    /** This language name */
    protected String name;

    /** The supported language table */
    protected HashMap languages;

    /** The code-generation logicsheet cache */
    protected Store logicsheetCache;

    /** The markup language's namespace uri */
    private String uri;

    /** The markup language's namespace prefix */
    private String prefix;

    /** Are attribute expressions to be expanded? */
    private boolean attrInterpolation;

    /** Are text expressions to be expanded? */
    private boolean textInterpolation;

    /** The service manager */
    protected ServiceManager manager;

    /** The URL factory source resolver used to resolve URIs */
    private SourceResolver resolver;


    /**
     * Stores the list of logicsheets required by the currently
     * loaded program.
     */
    private final LinkedList logicSheetList = new LinkedList();


    /** The default constructor. */
    public AbstractMarkupLanguage() {
        // Initialize language table
        this.languages = new HashMap();
    }

    /**
     * Process additional configuration. Load supported programming
     * language definitions
     *
     * @param conf The language configuration
     * @exception ConfigurationException If an error occurs loading logichseets
     */
    public void configure(Configuration conf) throws ConfigurationException {
        try {
            this.name = conf.getAttribute("name");

            // Cannot use Parameterizable because parameterize() is called
            // after configure(), and <xsp-language> param's are already
            // needed for processing logicsheet definitions.
            Parameters params = Parameters.fromConfiguration(conf);
            this.uri = params.getParameter("uri");
            this.prefix = params.getParameter("prefix", null);
            this.attrInterpolation =
                params.getParameterAsBoolean(ATTR_INTERPOLATION, false);
            this.textInterpolation =
                params.getParameterAsBoolean(TEXT_INTERPOLATION, false);

            // Set up each target-language
            Configuration[] l = conf.getChildren("target-language");
            for (int i = 0; i < l.length; i++) {
                LanguageDescriptor language = new LanguageDescriptor();
                language.setName(l[i].getAttribute("name"));

                // Create & Store the core logicsheet
                Logicsheet logicsheet = createLogicsheet(l[i], false);
                language.setLogicsheet(logicsheet.getSystemId());

                // Set up each built-in logicsheet
                Configuration[] n = l[i].getChildren("builtin-logicsheet");
                for (int j = 0; j < n.length; j++) {
                    // Create & Store the named logicsheets
                    NamedLogicsheet namedLogicsheet =
                        (NamedLogicsheet) createLogicsheet(n[j], true);

                    language.addNamedLogicsheet(
                            namedLogicsheet.getURI(),
                            namedLogicsheet.getPrefix(),
                            namedLogicsheet.getSystemId());
                }

                this.languages.put(language.getName(), language);
            }
        } catch (Exception e) {
          getLogger().warn("Configuration Error: " + e.getMessage(), e);
          throw new ConfigurationException("AbstractMarkupLanguage: "
                                           + e.getMessage(), e);
        }
    }

    /**
     * Abstract out the Logicsheet creation.  Handles both Named and regular logicsheets.
     */
    private Logicsheet createLogicsheet(Configuration configuration, boolean named)
            throws Exception
    {
        Parameters params = Parameters.fromConfiguration(configuration);

        Logicsheet logicsheet;
        if (named) {
            String location = params.getParameter("href", null);
            String uri = params.getParameter("uri", null);
            String prefix = params.getParameter("prefix", null);

            NamedLogicsheet namedLogicsheet
                = new NamedLogicsheet(location, manager,
                                      resolver, getLogicsheetFilter());
            namedLogicsheet.setURI(uri);
            namedLogicsheet.setPrefix(prefix);
            logicsheet = namedLogicsheet;
        } else {
            String location = params.getParameter("core-logicsheet", null);
            logicsheet = new Logicsheet(location, manager,
                                        resolver, getLogicsheetFilter());
        }

        logicsheet.enableLogging(getLogger());

        String logicsheetName = logicsheet.getSystemId();
        logicsheetCache.store(CACHE_PREFIX + logicsheetName, logicsheet);

        return logicsheet;
    }

    /**
     * Set the global service manager.
     * @param manager The sitemap-specified service manager
     */
    public void service(ServiceManager manager) throws ServiceException {
        this.manager = manager;

        // Initialize logicsheet cache
        this.logicsheetCache = (Store) manager.lookup(Store.TRANSIENT_STORE);

        // Initialize the source resolver
        this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
    }

    /**
     * Recycle this component: clear logic sheet list and dependencies.
     */
    public void recycle() {
        this.logicSheetList.clear();
    }

    /**
     * Release all resources.
     */
    public void dispose() {
        this.manager.release(this.logicsheetCache);
        this.logicsheetCache = null;

        this.manager.release(this.resolver);
        this.resolver = null;
        this.manager = null;
        this.languages.clear();
    }

    /**
     * Return the markup language name. Two markup languages are
     * well-know at the moment: sitemap and xsp.
     *
     * @return The language name.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Returns the namespace URI for this language.
     */
    public String getURI() {
        return this.uri;
    }

    /**
     * Returns the namespace prefix for this language.
     */
    public String getPrefix() {
        return this.prefix;
    }

    /**
     * Returns true if expansion of attribute expressions is enabled
     * for this language.
     */
    public boolean hasAttrInterpolation() {
        return this.attrInterpolation;
    }

    /**
     * Returns true if expansion of expressions inside text nodes is enabled
     * for this language.
     */
    public boolean hasTextInterpolation() {
        return this.textInterpolation;
    }

    /**
     * Return the source document's encoding. This can be <code>null</code> for
     * the platform's default encoding. The default implementation returns
     * <code>null</code>, but derived classes may override it if encoding applies to
     * their concrete languages.
     *
     * FIXME: There should be a way to get the
     * XML document's encoding as seen by the parser; unfortunately, this
     * information is not returned by current DOM or SAX parsers...
     *
     * @return The document-specified encoding
     */
    public String getEncoding() {
        return null;
    }

    /**
     * Returns a filter that chains on the fly the requested
     * transformers for source code generation. This method scans the
     * input SAX events for built-in logicsheet declared as namespace
     * attribute on the root element. Derived class should overide
     * this method and the public inner class in order to add more
     * specif action and to build a more specific transformer chain.
     *
     * @param logicsheetMarkupGenerator the logicsheet markup generator
     * @return XMLFilter the filter that build on the fly the transformer chain
     */
    protected TransformerChainBuilderFilter getTransformerChainBuilder(
        LogicsheetCodeGenerator logicsheetMarkupGenerator)
    {
        return new TransformerChainBuilderFilter(logicsheetMarkupGenerator);
    }

    /**
     * Prepare the input source for logicsheet processing and code
     * generation with a preprocess filter.  The return
     * <code>XMLFilter</code> object is the first filter on the
     * transformer chain.  The default implementation does nothing by
     * returning a identity filter, but derived classes should (at
     * least) use the passed programming language to quote
     * <code>Strings</code>
     *
     * @param filename The source filename
     * @param language The target programming language
     * @return The preprocess filter
     */
    protected AbstractXMLPipe getPreprocessFilter(String filename,
                                                  AbstractXMLPipe filter,
                                                  ProgrammingLanguage language)
    {
        // No-op
        return filter;
    }

    /**
     * Add a dependency on an external file to the document for inclusion in
     * generated code. This is used to populate a list of <code>File</code>'s
     * tested for change on each invocation; this information is used to assert whether regeneration is necessary.
     *
     * @param location The file path of the dependent file
     * @see AbstractMarkupLanguage
     * @see org.apache.cocoon.generation.ServerPagesGenerator
     * @see org.apache.cocoon.generation.AbstractServerPage
     */
    protected abstract void addDependency(String location);

    /**
     * Generate source code from the input document for the target
     * <code>ProgrammingLanguage</code>. After preprocessing the input
     * document, this method applies logicsheets in the following
     * order:
     *
     * <ul>
     * <li>User-defined logicsheets</li>
     * <li>Namespace-mapped logicsheets</li>
     * <li>Language-specific logicsheet</li>
     * </ul>
     *
     * @param source The input source
     * @param filename The input document's original filename
     * @param programmingLanguage The target programming language
     * @return The generated source code
     * @exception Exception If an error occurs during code generation
     */
    public String generateCode(Source source,
                               String filename,
                               ProgrammingLanguage programmingLanguage)
            throws Exception {

        String languageName = programmingLanguage.getLanguageName();
        LanguageDescriptor language = (LanguageDescriptor)this.languages.get(languageName);
        if (language == null) {
            throw new IllegalArgumentException("Unsupported programming language: " + languageName);
        }

        // Create code generator
        LogicsheetCodeGenerator codeGenerator = new LogicsheetCodeGenerator();
        codeGenerator.enableLogging(getLogger());
        codeGenerator.initialize();
        // Set the transformer chain builder filter
        TransformerChainBuilderFilter tranBuilder =
                getTransformerChainBuilder(codeGenerator);
        tranBuilder.setLanguageDescriptor(language);

        // Get the needed preprocess filter
        AbstractXMLPipe preprocessor = getPreprocessFilter(filename, tranBuilder, programmingLanguage);
        return codeGenerator.generateCode(source, preprocessor);
    }

    /**
     * Add logicsheet list to the code generator.
     * @param codeGenerator The code generator
     */
    protected void addLogicsheetsToGenerator(LogicsheetCodeGenerator codeGenerator)
        throws MalformedURLException, IOException, SAXException, ProcessingException {

        if (codeGenerator == null) {
            getLogger().debug("This should never happen: codeGenerator is null");
            throw new SAXException("codeGenerator must never be null.");
        }

        // Walk backwards and remove duplicates.
        LinkedList newLogicSheetList = new LinkedList();
        for(int i = logicSheetList.size()-1; i>=0; i--) {
            Logicsheet logicsheet = (Logicsheet) logicSheetList.get(i);
            if(newLogicSheetList.indexOf(logicsheet) == -1)
                newLogicSheetList.addFirst(logicsheet);
        }

        // Add the list of logicsheets now.
        Iterator iterator = newLogicSheetList.iterator();
        while(iterator.hasNext()) {
            Logicsheet logicsheet = (Logicsheet) iterator.next();
            codeGenerator.addLogicsheet(logicsheet);
        }
    }

    /**
     * Add a logicsheet to the code generator.
     * @param language Target programming language of the logicsheet
     * @param logicsheetLocation Location of the logicsheet to be added
     * @exception MalformedURLException If location is invalid
     * @exception IOException IO Error
     * @exception SAXException Logicsheet parse error
     */
    protected void addLogicsheetToList(LanguageDescriptor language,
                                       String logicsheetLocation)
        throws IOException, SAXException, ProcessingException
    {
        Logicsheet logicsheet = (Logicsheet)logicsheetCache.get(CACHE_PREFIX + logicsheetLocation);
        if (logicsheet == null) {
            Source inputSource = null;
            try {
                // Logicsheet is reusable (across multiple XSPs) object,
                // and it is resolved via urlResolver, and not via per-request
                // temporary resolver.
                inputSource = this.resolver.resolveURI(logicsheetLocation);

                // Resolver (local) could not be used as it is temporary
                // (per-request) object, yet Logicsheet is being cached and reused
                // across multiple requests. "Global" url-factory-based resolver
                // passed to the Logicsheet.
                logicsheet = new Logicsheet(inputSource, manager,
                                            resolver, getLogicsheetFilter());
                logicsheetCache.store(CACHE_PREFIX + logicsheet.getSystemId(), logicsheet);
            } catch (SourceException se) {
                throw SourceUtil.handle(se);
            } finally {
                this.resolver.release( inputSource );
            }
        }
        String logicsheetName = logicsheet.getSystemId();

        if (getLogger().isDebugEnabled()) {
            getLogger().debug("addLogicsheetToList: "
                + "name: " + logicsheetName
                + ", location: " + logicsheetLocation
                + ", instance: " + logicsheet);
        }

        if (logicsheetName.startsWith(FILE)) {
            String filename = logicsheetName.substring(FILE.length());
            addDependency(filename);
            getLogger().debug("addLogicsheetToList: "
                + "adding dependency on file " + filename);
        }

        logicSheetList.add(logicsheet);

        Map namespaces = logicsheet.getNamespaceURIs();
        if(!logicsheetLocation.equals(language.getLogicsheet())) {
            if(namespaces != null && namespaces.size() > 0) {
                Iterator iter = namespaces.keySet().iterator();
                while(iter.hasNext()) {
                    String namespace = (String) iter.next();
                    String namedLogicsheetName = language.getNamedLogicsheetByURI(namespace);
                    if(namedLogicsheetName!= null
                        && !logicsheetLocation.equals(namedLogicsheetName)) {
                        getLogger().debug("Adding embedded logic sheet for "
                            + namespace + ": " + namedLogicsheetName);
                        // Add embedded logic sheets too.
                        addLogicsheetToList(language, namedLogicsheetName);
                    }
                }
            }
        }
    }

    /**
     * Return the optional filter to prepocess logicsheets.
     */
    protected LogicsheetFilter getLogicsheetFilter() {
        return new LogicsheetFilter();
    }

    //
    // Inner classes
    //

    /** This class holds transient information about a target programming language. */
    protected static class LanguageDescriptor {
        /** The progamming language name */
        protected String name;

        /** The progamming language core logicsheet */
        protected String logicsheet;

        /** The list of built-in logicsheets defined for this target language */
        protected HashMap namedLogicsheets;

        /** The default constructor */
        protected LanguageDescriptor() {
            this.namedLogicsheets = new HashMap();
        }

        /**
         * Set the programming language's name
         * @param name The programming language's name
         */
        protected void setName(String name) {
            this.name = name;
        }

        /**
         * Return the programming language's name
         * @return The programming language's name
         */
        protected String getName() {
            return this.name;
        }

        /**
         * Set the programming language's core logichseet location
         * @param logicsheet The programming language's core logichseet location
         */
        protected void setLogicsheet(String logicsheet) {
            this.logicsheet = logicsheet;
        }

        /**
         * Return the programming language's core logichseet location
         * @return The programming language's core logichseet location
         */
        protected String getLogicsheet() {
            return this.logicsheet;
        }

        /**
         * Add a namespace-mapped logicsheet to this language
         * @param prefix The logichseet's namespace prefix
         * @param uri The logichseet's namespace uri
         * @param namedLogicsheet The logichseet's location
         */
        protected void addNamedLogicsheet(String uri, String prefix, String namedLogicsheet) {
            this.namedLogicsheets.put(uri, namedLogicsheet);
        }

        /**
         * Return a namespace-mapped logicsheet given its uri
         * @return The namespace-mapped logicsheet
         */
        protected String getNamedLogicsheetByURI(String uri) {
            return (String)this.namedLogicsheets.get(uri);
        }
    }


    /**
     * An XMLFilter that build the chain of transformers on the fly.
     * Each time a stylesheet is found, a call to the code generator is done
     * to add the new transformer at the end of the current transformer chain.
     */
    public class TransformerChainBuilderFilter extends AbstractXMLPipe {
        /** The markup generator */
        protected LogicsheetCodeGenerator logicsheetMarkupGenerator;

        /** the language description */
        protected LanguageDescriptor language;

        private boolean isRootElem;
        private List startPrefixes;

        /**
         * the constructor depends on the code generator
         * @param logicsheetMarkupGenerator The code generator
         */
        protected TransformerChainBuilderFilter(LogicsheetCodeGenerator logicsheetMarkupGenerator) {
            this.logicsheetMarkupGenerator = logicsheetMarkupGenerator;
        }

        /**
         * This method should be called prior to receiving any SAX event.
         * Indeed the language information is needed to get the core stylesheet.
         * @param language the language in used
         */
        protected void setLanguageDescriptor(LanguageDescriptor language) {
            this.language = language;
        }

        /** @see org.xml.sax.ContentHandler */
        public void startDocument() throws SAXException {
            isRootElem = true;
            startPrefixes = new ArrayList();
        }

        /** @see org.xml.sax.ContentHandler */
        public void startPrefixMapping(String prefix, String uri) throws SAXException {
            if (!isRootElem) {
                super.startPrefixMapping(prefix, uri);
            } else {
                // Cache the prefix mapping
                String[] prefixNamingArray = new String[2];
                prefixNamingArray[0] = prefix;
                prefixNamingArray[1] = uri;
                this.startPrefixes.add(prefixNamingArray);
            }
        }

        /** @see org.xml.sax.ContentHandler */
        public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
            if (isRootElem) {
                isRootElem = false;
                try {
                    // Add namespace-mapped logicsheets
                    int prefixesCount = this.startPrefixes.size();
                    for (int i = 0; i < prefixesCount; i++) {
                        String[] prefixNamingArray = (String[]) this.startPrefixes.get(i);
                        String namedLogicsheetName = this.language.getNamedLogicsheetByURI(prefixNamingArray[1]);
                        if (namedLogicsheetName != null) {
                            AbstractMarkupLanguage.this.addLogicsheetToList(language, namedLogicsheetName);
                        }
                    }

                    // Add the language stylesheet (Always the last one)
                    AbstractMarkupLanguage.this.addLogicsheetToList(language, this.language.getLogicsheet());
                    AbstractMarkupLanguage.this.addLogicsheetsToGenerator(this.logicsheetMarkupGenerator);
                } catch (ProcessingException pe) {
                    throw new SAXException (pe);
                } catch (IOException ioe) {
                    throw new SAXException(ioe);
                }

                // All stylesheet have been configured and correctly setup.
                // Starts firing SAX events, especially the startDocument event,
                // and the cached prefixNaming.
                super.startDocument();
                int prefixesCount = this.startPrefixes.size();
                for (int i = 0; i < prefixesCount; i++) {
                    String[] prefixNamingArray = (String[]) this.startPrefixes.get(i);
                    super.startPrefixMapping(prefixNamingArray[0], prefixNamingArray[1]);
                }
            }
            // Call super method
            super.startElement(namespaceURI, localName, qName, atts);
        }
    }
}
TOP

Related Classes of org.apache.cocoon.components.language.markup.AbstractMarkupLanguage$LanguageDescriptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.