KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > components > language > markup > AbstractMarkupLanguage


1 /*
2  * Copyright 1999-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.cocoon.components.language.markup;
17
18 import org.apache.avalon.framework.activity.Disposable;
19 import org.apache.avalon.framework.configuration.Configurable;
20 import org.apache.avalon.framework.configuration.Configuration;
21 import org.apache.avalon.framework.configuration.ConfigurationException;
22 import org.apache.avalon.framework.logger.AbstractLogEnabled;
23 import org.apache.avalon.framework.parameters.Parameters;
24 import org.apache.avalon.framework.service.ServiceException;
25 import org.apache.avalon.framework.service.ServiceManager;
26 import org.apache.avalon.framework.service.Serviceable;
27
28 import org.apache.avalon.excalibur.pool.Recyclable;
29 import org.apache.excalibur.source.Source;
30 import org.apache.excalibur.source.SourceException;
31 import org.apache.excalibur.source.SourceResolver;
32
33 import org.apache.cocoon.ProcessingException;
34 import org.apache.cocoon.xml.AbstractXMLPipe;
35 import org.apache.cocoon.components.language.programming.ProgrammingLanguage;
36 import org.apache.cocoon.components.source.SourceUtil;
37 import org.apache.excalibur.store.Store;
38 import org.apache.cocoon.util.HashMap;
39
40 import org.xml.sax.Attributes JavaDoc;
41 import org.xml.sax.SAXException JavaDoc;
42
43 import java.io.IOException JavaDoc;
44 import java.net.MalformedURLException JavaDoc;
45 import java.util.ArrayList JavaDoc;
46 import java.util.Iterator JavaDoc;
47 import java.util.LinkedList JavaDoc;
48 import java.util.List JavaDoc;
49 import java.util.Map JavaDoc;
50
51 /**
52  * Base implementation of <code>MarkupLanguage</code>. This class uses
53  * logicsheets as the only means of code generation. Code generation
54  * should be decoupled from this context!!!
55  *
56  * @author <a HREF="mailto:ricardo@apache.org">Ricardo Rocha</a>
57  * @author <a HREF="mailto:dims@yahoo.com">Davanum Srinivas</a>
58  * @author <a HREF="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
59  * @version CVS $Id: AbstractMarkupLanguage.java 278591 2005-09-04 13:24:23Z pier $
60  */

61 public abstract class AbstractMarkupLanguage
62         extends AbstractLogEnabled
63         implements MarkupLanguage, Serviceable, Configurable, Recyclable, Disposable
64 {
65     /**
66      * Name "attr-interpolation" of boolean attribute to enable
67      * expression interpolation in attribute values.
68      */

69     public static final String JavaDoc ATTR_INTERPOLATION = "attr-interpolation";
70
71     /**
72      * Name "text-interpolation" of boolean attribute to enable
73      * expression interpolation inside text nodes.
74      */

75     public static final String JavaDoc TEXT_INTERPOLATION = "text-interpolation";
76
77     /** The 'file' URL protocol. */
78     private static final String JavaDoc FILE = "file:";
79
80     /** Prefix for cache keys to avoid name clash with the XSLTProcessor */
81     private static final String JavaDoc CACHE_PREFIX = "logicsheet:";
82
83     /** This language name */
84     protected String JavaDoc name;
85
86     /** The supported language table */
87     protected HashMap languages;
88
89     /** The code-generation logicsheet cache */
90     protected Store logicsheetCache;
91
92     /** The markup language's namespace uri */
93     private String JavaDoc uri;
94
95     /** The markup language's namespace prefix */
96     private String JavaDoc prefix;
97
98     /** Are attribute expressions to be expanded? */
99     private boolean attrInterpolation;
100
101     /** Are text expressions to be expanded? */
102     private boolean textInterpolation;
103
104     /** The service manager */
105     protected ServiceManager manager;
106
107     /** The URL factory source resolver used to resolve URIs */
108     private SourceResolver resolver;
109
110
111     /**
112      * Stores the list of logicsheets required by the currently
113      * loaded program.
114      */

115     private final LinkedList JavaDoc logicSheetList = new LinkedList JavaDoc();
116
117
118     /** The default constructor. */
119     public AbstractMarkupLanguage() {
120         // Initialize language table
121
this.languages = new HashMap();
122     }
123
124     /**
125      * Process additional configuration. Load supported programming
126      * language definitions
127      *
128      * @param conf The language configuration
129      * @exception ConfigurationException If an error occurs loading logichseets
130      */

131     public void configure(Configuration conf) throws ConfigurationException {
132         try {
133             this.name = conf.getAttribute("name");
134
135             // Cannot use Parameterizable because parameterize() is called
136
// after configure(), and <xsp-language> param's are already
137
// needed for processing logicsheet definitions.
138
Parameters params = Parameters.fromConfiguration(conf);
139             this.uri = params.getParameter("uri");
140             this.prefix = params.getParameter("prefix", null);
141             this.attrInterpolation =
142                 params.getParameterAsBoolean(ATTR_INTERPOLATION, false);
143             this.textInterpolation =
144                 params.getParameterAsBoolean(TEXT_INTERPOLATION, false);
145
146             // Set up each target-language
147
Configuration[] l = conf.getChildren("target-language");
148             for (int i = 0; i < l.length; i++) {
149                 LanguageDescriptor language = new LanguageDescriptor();
150                 language.setName(l[i].getAttribute("name"));
151
152                 // Create & Store the core logicsheet
153
Logicsheet logicsheet = createLogicsheet(l[i], false);
154                 language.setLogicsheet(logicsheet.getSystemId());
155
156                 // Set up each built-in logicsheet
157
Configuration[] n = l[i].getChildren("builtin-logicsheet");
158                 for (int j = 0; j < n.length; j++) {
159                     // Create & Store the named logicsheets
160
NamedLogicsheet namedLogicsheet =
161                         (NamedLogicsheet) createLogicsheet(n[j], true);
162
163                     language.addNamedLogicsheet(
164                             namedLogicsheet.getURI(),
165                             namedLogicsheet.getPrefix(),
166                             namedLogicsheet.getSystemId());
167                 }
168
169                 this.languages.put(language.getName(), language);
170             }
171         } catch (Exception JavaDoc e) {
172           getLogger().warn("Configuration Error: " + e.getMessage(), e);
173           throw new ConfigurationException("AbstractMarkupLanguage: "
174                                            + e.getMessage(), e);
175         }
176     }
177
178     /**
179      * Abstract out the Logicsheet creation. Handles both Named and regular logicsheets.
180      */

181     private Logicsheet createLogicsheet(Configuration configuration, boolean named)
182             throws Exception JavaDoc
183     {
184         Parameters params = Parameters.fromConfiguration(configuration);
185
186         Logicsheet logicsheet;
187         if (named) {
188             String JavaDoc location = params.getParameter("href", null);
189             String JavaDoc uri = params.getParameter("uri", null);
190             String JavaDoc prefix = params.getParameter("prefix", null);
191
192             NamedLogicsheet namedLogicsheet
193                 = new NamedLogicsheet(location, manager,
194                                       resolver, getLogicsheetFilter());
195             namedLogicsheet.setURI(uri);
196             namedLogicsheet.setPrefix(prefix);
197             logicsheet = namedLogicsheet;
198         } else {
199             String JavaDoc location = params.getParameter("core-logicsheet", null);
200             logicsheet = new Logicsheet(location, manager,
201                                         resolver, getLogicsheetFilter());
202         }
203
204         logicsheet.enableLogging(getLogger());
205
206         String JavaDoc logicsheetName = logicsheet.getSystemId();
207         logicsheetCache.store(CACHE_PREFIX + logicsheetName, logicsheet);
208
209         return logicsheet;
210     }
211
212     /**
213      * Set the global service manager.
214      * @param manager The sitemap-specified service manager
215      */

216     public void service(ServiceManager manager) throws ServiceException {
217         this.manager = manager;
218
219         // Initialize logicsheet cache
220
this.logicsheetCache = (Store) manager.lookup(Store.TRANSIENT_STORE);
221
222         // Initialize the source resolver
223
this.resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE);
224     }
225
226     /**
227      * Recycle this component: clear logic sheet list and dependencies.
228      */

229     public void recycle() {
230         this.logicSheetList.clear();
231     }
232
233     /**
234      * Release all resources.
235      */

236     public void dispose() {
237         this.manager.release(this.logicsheetCache);
238         this.logicsheetCache = null;
239
240         this.manager.release(this.resolver);
241         this.resolver = null;
242         this.manager = null;
243         this.languages.clear();
244     }
245
246     /**
247      * Return the markup language name. Two markup languages are
248      * well-know at the moment: sitemap and xsp.
249      *
250      * @return The language name.
251      */

252     public String JavaDoc getName() {
253         return this.name;
254     }
255
256     /**
257      * Returns the namespace URI for this language.
258      */

259     public String JavaDoc getURI() {
260         return this.uri;
261     }
262
263     /**
264      * Returns the namespace prefix for this language.
265      */

266     public String JavaDoc getPrefix() {
267         return this.prefix;
268     }
269
270     /**
271      * Returns true if expansion of attribute expressions is enabled
272      * for this language.
273      */

274     public boolean hasAttrInterpolation() {
275         return this.attrInterpolation;
276     }
277
278     /**
279      * Returns true if expansion of expressions inside text nodes is enabled
280      * for this language.
281      */

282     public boolean hasTextInterpolation() {
283         return this.textInterpolation;
284     }
285
286     /**
287      * Return the source document's encoding. This can be <code>null</code> for
288      * the platform's default encoding. The default implementation returns
289      * <code>null</code>, but derived classes may override it if encoding applies to
290      * their concrete languages.
291      *
292      * FIXME: There should be a way to get the
293      * XML document's encoding as seen by the parser; unfortunately, this
294      * information is not returned by current DOM or SAX parsers...
295      *
296      * @return The document-specified encoding
297      */

298     public String JavaDoc getEncoding() {
299         return null;
300     }
301
302     /**
303      * Returns a filter that chains on the fly the requested
304      * transformers for source code generation. This method scans the
305      * input SAX events for built-in logicsheet declared as namespace
306      * attribute on the root element. Derived class should overide
307      * this method and the public inner class in order to add more
308      * specif action and to build a more specific transformer chain.
309      *
310      * @param logicsheetMarkupGenerator the logicsheet markup generator
311      * @return XMLFilter the filter that build on the fly the transformer chain
312      */

313     protected TransformerChainBuilderFilter getTransformerChainBuilder(
314         LogicsheetCodeGenerator logicsheetMarkupGenerator)
315     {
316         return new TransformerChainBuilderFilter(logicsheetMarkupGenerator);
317     }
318
319     /**
320      * Prepare the input source for logicsheet processing and code
321      * generation with a preprocess filter. The return
322      * <code>XMLFilter</code> object is the first filter on the
323      * transformer chain. The default implementation does nothing by
324      * returning a identity filter, but derived classes should (at
325      * least) use the passed programming language to quote
326      * <code>Strings</code>
327      *
328      * @param filename The source filename
329      * @param language The target programming language
330      * @return The preprocess filter
331      */

332     protected AbstractXMLPipe getPreprocessFilter(String JavaDoc filename,
333                                                   AbstractXMLPipe filter,
334                                                   ProgrammingLanguage language)
335     {
336         // No-op
337
return filter;
338     }
339
340     /**
341      * Add a dependency on an external file to the document for inclusion in
342      * generated code. This is used to populate a list of <code>File</code>'s
343      * tested for change on each invocation; this information is used to assert whether regeneration is necessary.
344      *
345      * @param location The file path of the dependent file
346      * @see AbstractMarkupLanguage
347      * @see org.apache.cocoon.generation.ServerPagesGenerator
348      * @see org.apache.cocoon.generation.AbstractServerPage
349      */

350     protected abstract void addDependency(String JavaDoc location);
351
352     /**
353      * Generate source code from the input document for the target
354      * <code>ProgrammingLanguage</code>. After preprocessing the input
355      * document, this method applies logicsheets in the following
356      * order:
357      *
358      * <ul>
359      * <li>User-defined logicsheets</li>
360      * <li>Namespace-mapped logicsheets</li>
361      * <li>Language-specific logicsheet</li>
362      * </ul>
363      *
364      * @param source The input source
365      * @param filename The input document's original filename
366      * @param programmingLanguage The target programming language
367      * @return The generated source code
368      * @exception Exception If an error occurs during code generation
369      */

370     public String JavaDoc generateCode(Source source,
371                                String JavaDoc filename,
372                                ProgrammingLanguage programmingLanguage)
373             throws Exception JavaDoc {
374
375         String JavaDoc languageName = programmingLanguage.getLanguageName();
376         LanguageDescriptor language = (LanguageDescriptor)this.languages.get(languageName);
377         if (language == null) {
378             throw new IllegalArgumentException JavaDoc("Unsupported programming language: " + languageName);
379         }
380
381         // Create code generator
382
LogicsheetCodeGenerator codeGenerator = new LogicsheetCodeGenerator();
383         codeGenerator.enableLogging(getLogger());
384         codeGenerator.initialize();
385         // Set the transformer chain builder filter
386
TransformerChainBuilderFilter tranBuilder =
387                 getTransformerChainBuilder(codeGenerator);
388         tranBuilder.setLanguageDescriptor(language);
389
390         // Get the needed preprocess filter
391
AbstractXMLPipe preprocessor = getPreprocessFilter(filename, tranBuilder, programmingLanguage);
392         return codeGenerator.generateCode(source, preprocessor);
393     }
394
395     /**
396      * Add logicsheet list to the code generator.
397      * @param codeGenerator The code generator
398      */

399     protected void addLogicsheetsToGenerator(LogicsheetCodeGenerator codeGenerator)
400         throws MalformedURLException JavaDoc, IOException JavaDoc, SAXException JavaDoc, ProcessingException {
401
402         if (codeGenerator == null) {
403             getLogger().debug("This should never happen: codeGenerator is null");
404             throw new SAXException JavaDoc("codeGenerator must never be null.");
405         }
406
407         // Walk backwards and remove duplicates.
408
LinkedList JavaDoc newLogicSheetList = new LinkedList JavaDoc();
409         for(int i = logicSheetList.size()-1; i>=0; i--) {
410             Logicsheet logicsheet = (Logicsheet) logicSheetList.get(i);
411             if(newLogicSheetList.indexOf(logicsheet) == -1)
412                 newLogicSheetList.addFirst(logicsheet);
413         }
414
415         // Add the list of logicsheets now.
416
Iterator JavaDoc iterator = newLogicSheetList.iterator();
417         while(iterator.hasNext()) {
418             Logicsheet logicsheet = (Logicsheet) iterator.next();
419             codeGenerator.addLogicsheet(logicsheet);
420         }
421     }
422
423     /**
424      * Add a logicsheet to the code generator.
425      * @param language Target programming language of the logicsheet
426      * @param logicsheetLocation Location of the logicsheet to be added
427      * @exception MalformedURLException If location is invalid
428      * @exception IOException IO Error
429      * @exception SAXException Logicsheet parse error
430      */

431     protected void addLogicsheetToList(LanguageDescriptor language,
432                                        String JavaDoc logicsheetLocation)
433         throws IOException JavaDoc, SAXException JavaDoc, ProcessingException
434     {
435         Logicsheet logicsheet = (Logicsheet)logicsheetCache.get(CACHE_PREFIX + logicsheetLocation);
436         if (logicsheet == null) {
437             Source inputSource = null;
438             try {
439                 // Logicsheet is reusable (across multiple XSPs) object,
440
// and it is resolved via urlResolver, and not via per-request
441
// temporary resolver.
442
inputSource = this.resolver.resolveURI(logicsheetLocation);
443
444                 // Resolver (local) could not be used as it is temporary
445
// (per-request) object, yet Logicsheet is being cached and reused
446
// across multiple requests. "Global" url-factory-based resolver
447
// passed to the Logicsheet.
448
logicsheet = new Logicsheet(inputSource, manager,
449                                             resolver, getLogicsheetFilter());
450                 logicsheetCache.store(CACHE_PREFIX + logicsheet.getSystemId(), logicsheet);
451             } catch (SourceException se) {
452                 throw SourceUtil.handle(se);
453             } finally {
454                 this.resolver.release( inputSource );
455             }
456         }
457         String JavaDoc logicsheetName = logicsheet.getSystemId();
458
459         if (getLogger().isDebugEnabled()) {
460             getLogger().debug("addLogicsheetToList: "
461                 + "name: " + logicsheetName
462                 + ", location: " + logicsheetLocation
463                 + ", instance: " + logicsheet);
464         }
465
466         if (logicsheetName.startsWith(FILE)) {
467             String JavaDoc filename = logicsheetName.substring(FILE.length());
468             addDependency(filename);
469             getLogger().debug("addLogicsheetToList: "
470                 + "adding dependency on file " + filename);
471         }
472
473         logicSheetList.add(logicsheet);
474
475         Map namespaces = logicsheet.getNamespaceURIs();
476         if(!logicsheetLocation.equals(language.getLogicsheet())) {
477             if(namespaces != null && namespaces.size() > 0) {
478                 Iterator JavaDoc iter = namespaces.keySet().iterator();
479                 while(iter.hasNext()) {
480                     String JavaDoc namespace = (String JavaDoc) iter.next();
481                     String JavaDoc namedLogicsheetName = language.getNamedLogicsheetByURI(namespace);
482                     if(namedLogicsheetName!= null
483                         && !logicsheetLocation.equals(namedLogicsheetName)) {
484                         getLogger().debug("Adding embedded logic sheet for "
485                             + namespace + ": " + namedLogicsheetName);
486                         // Add embedded logic sheets too.
487
addLogicsheetToList(language, namedLogicsheetName);
488                     }
489                 }
490             }
491         }
492     }
493
494     /**
495      * Return the optional filter to prepocess logicsheets.
496      */

497     protected LogicsheetFilter getLogicsheetFilter() {
498         return new LogicsheetFilter();
499     }
500
501     //
502
// Inner classes
503
//
504

505     /** This class holds transient information about a target programming language. */
506     protected static class LanguageDescriptor {
507         /** The progamming language name */
508         protected String JavaDoc name;
509
510         /** The progamming language core logicsheet */
511         protected String JavaDoc logicsheet;
512
513         /** The list of built-in logicsheets defined for this target language */
514         protected HashMap namedLogicsheets;
515
516         /** The default constructor */
517         protected LanguageDescriptor() {
518             this.namedLogicsheets = new HashMap();
519         }
520
521         /**
522          * Set the programming language's name
523          * @param name The programming language's name
524          */

525         protected void setName(String JavaDoc name) {
526             this.name = name;
527         }
528
529         /**
530          * Return the programming language's name
531          * @return The programming language's name
532          */

533         protected String JavaDoc getName() {
534             return this.name;
535         }
536
537         /**
538          * Set the programming language's core logichseet location
539          * @param logicsheet The programming language's core logichseet location
540          */

541         protected void setLogicsheet(String JavaDoc logicsheet) {
542             this.logicsheet = logicsheet;
543         }
544
545         /**
546          * Return the programming language's core logichseet location
547          * @return The programming language's core logichseet location
548          */

549         protected String JavaDoc getLogicsheet() {
550             return this.logicsheet;
551         }
552
553         /**
554          * Add a namespace-mapped logicsheet to this language
555          * @param prefix The logichseet's namespace prefix
556          * @param uri The logichseet's namespace uri
557          * @param namedLogicsheet The logichseet's location
558          */

559         protected void addNamedLogicsheet(String JavaDoc uri, String JavaDoc prefix, String JavaDoc namedLogicsheet) {
560             this.namedLogicsheets.put(uri, namedLogicsheet);
561         }
562
563         /**
564          * Return a namespace-mapped logicsheet given its uri
565          * @return The namespace-mapped logicsheet
566          */

567         protected String JavaDoc getNamedLogicsheetByURI(String JavaDoc uri) {
568             return (String JavaDoc)this.namedLogicsheets.get(uri);
569         }
570     }
571
572
573     /**
574      * An XMLFilter that build the chain of transformers on the fly.
575      * Each time a stylesheet is found, a call to the code generator is done
576      * to add the new transformer at the end of the current transformer chain.
577      */

578     public class TransformerChainBuilderFilter extends AbstractXMLPipe {
579         /** The markup generator */
580         protected LogicsheetCodeGenerator logicsheetMarkupGenerator;
581
582         /** the language description */
583         protected LanguageDescriptor language;
584
585         private boolean isRootElem;
586         private List JavaDoc startPrefixes;
587
588         /**
589          * the constructor depends on the code generator
590          * @param logicsheetMarkupGenerator The code generator
591          */

592         protected TransformerChainBuilderFilter(LogicsheetCodeGenerator logicsheetMarkupGenerator) {
593             this.logicsheetMarkupGenerator = logicsheetMarkupGenerator;
594         }
595
596         /**
597          * This method should be called prior to receiving any SAX event.
598          * Indeed the language information is needed to get the core stylesheet.
599          * @param language the language in used
600          */

601         protected void setLanguageDescriptor(LanguageDescriptor language) {
602             this.language = language;
603         }
604
605         /** @see org.xml.sax.ContentHandler */
606         public void startDocument() throws SAXException JavaDoc {
607             isRootElem = true;
608             startPrefixes = new ArrayList JavaDoc();
609         }
610
611         /** @see org.xml.sax.ContentHandler */
612         public void startPrefixMapping(String JavaDoc prefix, String JavaDoc uri) throws SAXException JavaDoc {
613             if (!isRootElem) {
614                 super.startPrefixMapping(prefix, uri);
615             } else {
616                 // Cache the prefix mapping
617
String JavaDoc[] prefixNamingArray = new String JavaDoc[2];
618                 prefixNamingArray[0] = prefix;
619                 prefixNamingArray[1] = uri;
620                 this.startPrefixes.add(prefixNamingArray);
621             }
622         }
623
624         /** @see org.xml.sax.ContentHandler */
625         public void startElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc atts) throws SAXException JavaDoc {
626             if (isRootElem) {
627                 isRootElem = false;
628                 try {
629                     // Add namespace-mapped logicsheets
630
int prefixesCount = this.startPrefixes.size();
631                     for (int i = 0; i < prefixesCount; i++) {
632                         String JavaDoc[] prefixNamingArray = (String JavaDoc[]) this.startPrefixes.get(i);
633                         String JavaDoc namedLogicsheetName = this.language.getNamedLogicsheetByURI(prefixNamingArray[1]);
634                         if (namedLogicsheetName != null) {
635                             AbstractMarkupLanguage.this.addLogicsheetToList(language, namedLogicsheetName);
636                         }
637                     }
638
639                     // Add the language stylesheet (Always the last one)
640
AbstractMarkupLanguage.this.addLogicsheetToList(language, this.language.getLogicsheet());
641                     AbstractMarkupLanguage.this.addLogicsheetsToGenerator(this.logicsheetMarkupGenerator);
642                 } catch (ProcessingException pe) {
643                     throw new SAXException JavaDoc (pe);
644                 } catch (IOException JavaDoc ioe) {
645                     throw new SAXException JavaDoc(ioe);
646                 }
647
648                 // All stylesheet have been configured and correctly setup.
649
// Starts firing SAX events, especially the startDocument event,
650
// and the cached prefixNaming.
651
super.startDocument();
652                 int prefixesCount = this.startPrefixes.size();
653                 for (int i = 0; i < prefixesCount; i++) {
654                     String JavaDoc[] prefixNamingArray = (String JavaDoc[]) this.startPrefixes.get(i);
655                     super.startPrefixMapping(prefixNamingArray[0], prefixNamingArray[1]);
656                 }
657             }
658             // Call super method
659
super.startElement(namespaceURI, localName, qName, atts);
660         }
661     }
662 }
663
Popular Tags