Source Code Cross Referenced for TemplateParserImpl.java in  » Library » Tapestry » org » apache » tapestry » internal » services » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Library » Tapestry » org.apache.tapestry.internal.services 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        // Copyright 2006, 2007 The Apache Software Foundation
002:        //
003:        // Licensed under the Apache License, Version 2.0 (the "License");
004:        // you may not use this file except in compliance with the License.
005:        // You may obtain a copy of the License at
006:        //
007:        //     http://www.apache.org/licenses/LICENSE-2.0
008:        //
009:        // Unless required by applicable law or agreed to in writing, software
010:        // distributed under the License is distributed on an "AS IS" BASIS,
011:        // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012:        // See the License for the specific language governing permissions and
013:        // limitations under the License.
014:
015:        package org.apache.tapestry.internal.services;
016:
017:        import static org.apache.tapestry.ioc.IOCConstants.PERTHREAD_SCOPE;
018:        import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
019:        import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
020:
021:        import java.io.IOException;
022:        import java.net.URL;
023:        import java.util.List;
024:        import java.util.Map;
025:        import java.util.Set;
026:        import java.util.regex.Matcher;
027:        import java.util.regex.Pattern;
028:
029:        import org.apache.commons.logging.Log;
030:        import org.apache.tapestry.internal.parser.AttributeToken;
031:        import org.apache.tapestry.internal.parser.BlockToken;
032:        import org.apache.tapestry.internal.parser.BodyToken;
033:        import org.apache.tapestry.internal.parser.CDATAToken;
034:        import org.apache.tapestry.internal.parser.CommentToken;
035:        import org.apache.tapestry.internal.parser.ComponentTemplate;
036:        import org.apache.tapestry.internal.parser.ComponentTemplateImpl;
037:        import org.apache.tapestry.internal.parser.DTDToken;
038:        import org.apache.tapestry.internal.parser.EndElementToken;
039:        import org.apache.tapestry.internal.parser.ExpansionToken;
040:        import org.apache.tapestry.internal.parser.ParameterToken;
041:        import org.apache.tapestry.internal.parser.StartComponentToken;
042:        import org.apache.tapestry.internal.parser.StartElementToken;
043:        import org.apache.tapestry.internal.parser.TemplateToken;
044:        import org.apache.tapestry.internal.parser.TextToken;
045:        import org.apache.tapestry.ioc.Location;
046:        import org.apache.tapestry.ioc.Resource;
047:        import org.apache.tapestry.ioc.annotations.Scope;
048:        import org.apache.tapestry.ioc.internal.util.InternalUtils;
049:        import org.apache.tapestry.ioc.internal.util.LocationImpl;
050:        import org.apache.tapestry.ioc.internal.util.TapestryException;
051:        import org.xml.sax.Attributes;
052:        import org.xml.sax.ContentHandler;
053:        import org.xml.sax.EntityResolver;
054:        import org.xml.sax.InputSource;
055:        import org.xml.sax.Locator;
056:        import org.xml.sax.SAXException;
057:        import org.xml.sax.XMLReader;
058:        import org.xml.sax.ext.LexicalHandler;
059:        import org.xml.sax.helpers.XMLReaderFactory;
060:
061:        /**
062:         * Non-threadsafe implementation; the IOC service uses the perthread lifecycle.
063:         */
064:        @Scope(PERTHREAD_SCOPE)
065:        public class TemplateParserImpl implements  TemplateParser,
066:                LexicalHandler, ContentHandler, EntityResolver {
067:            private static final String MIXINS_ATTRIBUTE_NAME = "mixins";
068:
069:            private static final String TYPE_ATTRIBUTE_NAME = "type";
070:
071:            private static final String ID_ATTRIBUTE_NAME = "id";
072:
073:            public static final String TAPESTRY_SCHEMA_5_0_0 = "http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";
074:
075:            private XMLReader _reader;
076:
077:            // Resource being parsed
078:            private Resource _templateResource;
079:
080:            private Locator _locator;
081:
082:            private final List<TemplateToken> _tokens = newList();
083:
084:            // Non-blank ids from start component (<comp>) elements
085:
086:            private final Set<String> _componentIds = newSet();
087:
088:            // Used to accumulate text provided by the characters(). Even contiguous characters may be
089:            // broken up across multiple invocations due to parser internals. We accumulate those together
090:            // before forming a text token.
091:
092:            private final StringBuilder _textBuffer = new StringBuilder();
093:
094:            private Location _textStartLocation;
095:
096:            private boolean _textIsCData;
097:
098:            private boolean _insideBody;
099:
100:            private boolean _insideBodyErrorLogged;
101:
102:            private boolean _ignoreEvents;
103:
104:            private final Log _log;
105:
106:            private final Map<String, URL> _configuration;
107:
108:            // Note the use of the non-greedy modifier; this prevents the pattern from merging multiple
109:            // expansions on the same text line into a single large
110:            // but invalid expansion.
111:
112:            private final Pattern EXPANSION_PATTERN = Pattern.compile(
113:                    "\\$\\{\\s*(.*?)\\s*}", Pattern.MULTILINE);
114:
115:            public TemplateParserImpl(Log log, Map<String, URL> configuration) {
116:                _log = log;
117:                _configuration = configuration;
118:
119:                reset();
120:            }
121:
122:            private void reset() {
123:                _tokens.clear();
124:                _componentIds.clear();
125:                _templateResource = null;
126:                _locator = null;
127:                _textBuffer.setLength(0);
128:                _textStartLocation = null;
129:                _textIsCData = false;
130:                _insideBody = false;
131:                _insideBodyErrorLogged = false;
132:                _ignoreEvents = true;
133:            }
134:
135:            public ComponentTemplate parseTemplate(Resource templateResource) {
136:                if (_reader == null) {
137:                    try {
138:                        _reader = XMLReaderFactory.createXMLReader();
139:
140:                        _reader.setContentHandler(this );
141:
142:                        _reader.setEntityResolver(this );
143:
144:                        _reader
145:                                .setFeature(
146:                                        "http://xml.org/sax/features/namespace-prefixes",
147:                                        true);
148:
149:                        _reader
150:                                .setProperty(
151:                                        "http://xml.org/sax/properties/lexical-handler",
152:                                        this );
153:                    } catch (Exception ex) {
154:                        throw new RuntimeException(ServicesMessages
155:                                .newParserError(templateResource, ex), ex);
156:                    }
157:                }
158:
159:                URL resourceURL = templateResource.toURL();
160:
161:                if (resourceURL == null)
162:                    throw new RuntimeException(ServicesMessages
163:                            .missingTemplateResource(templateResource));
164:
165:                _templateResource = templateResource;
166:
167:                try {
168:                    InputSource source = new InputSource(resourceURL
169:                            .openStream());
170:
171:                    _reader.parse(source);
172:
173:                    return new ComponentTemplateImpl(_templateResource,
174:                            _tokens, _componentIds);
175:                } catch (Exception ex) {
176:                    // Some parsers get in an unknown state when an error occurs, and are are not
177:                    // subsequently useable.
178:
179:                    _reader = null;
180:
181:                    throw new TapestryException(ServicesMessages
182:                            .templateParseError(templateResource, ex),
183:                            getCurrentLocation(), ex);
184:                } finally {
185:                    reset();
186:                }
187:            }
188:
189:            public void setDocumentLocator(Locator locator) {
190:                _locator = locator;
191:            }
192:
193:            /** Accumulates the characters into a text buffer. */
194:            public void characters(char[] ch, int start, int length)
195:                    throws SAXException {
196:                if (_ignoreEvents)
197:                    return;
198:
199:                if (insideBody())
200:                    return;
201:
202:                if (_textBuffer.length() == 0)
203:                    _textStartLocation = getCurrentLocation();
204:
205:                _textBuffer.append(ch, start, length);
206:            }
207:
208:            /**
209:             * Adds tokens corresponding to the content in the text buffer. For a non-CDATA section, we also
210:             * search for expansions (thus we may add more than one token). Clears the text buffer.
211:             */
212:            private void processTextBuffer() {
213:                if (_textBuffer.length() == 0)
214:                    return;
215:
216:                String text = _textBuffer.toString();
217:
218:                if (_textIsCData) {
219:                    _tokens.add(new CDATAToken(text, _textStartLocation));
220:                } else {
221:                    addTokensForText(text);
222:                }
223:
224:                _textBuffer.setLength(0);
225:            }
226:
227:            /**
228:             * Scans the text, using a regular expression pattern, for expansion patterns, and adds
229:             * appropriate tokens for what it finds.
230:             * 
231:             * @param text
232:             */
233:            private void addTokensForText(String text) {
234:                Matcher matcher = EXPANSION_PATTERN.matcher(text);
235:
236:                int startx = 0;
237:
238:                // The big problem with all this code is that everything gets assigned to the
239:                // start of the text block, even if there are line breaks leading up to it.
240:                // That's going to take a lot more work and there are bigger fish to fry.
241:
242:                while (matcher.find()) {
243:                    int matchStart = matcher.start();
244:
245:                    if (matchStart != startx) {
246:                        String prefix = text.substring(startx, matchStart);
247:
248:                        _tokens.add(new TextToken(prefix, _textStartLocation));
249:                    }
250:
251:                    // Group 1 includes the real text of the expansion, which whitespace around the
252:                    // expression (but inside the curly
253:                    // braces) excluded.
254:
255:                    String expression = matcher.group(1);
256:
257:                    _tokens.add(new ExpansionToken(expression,
258:                            _textStartLocation));
259:
260:                    startx = matcher.end();
261:                }
262:
263:                // Catch anything after the final regexp match.
264:
265:                if (startx < text.length())
266:                    _tokens.add(new TextToken(text.substring(startx, text
267:                            .length()), _textStartLocation));
268:            }
269:
270:            public void startElement(String uri, String localName,
271:                    String qName, Attributes attributes) throws SAXException {
272:                _ignoreEvents = false;
273:
274:                if (_insideBody)
275:                    throw new IllegalStateException(ServicesMessages
276:                            .mayNotNestElementsInsideBody(localName));
277:
278:                // Add any accumulated text into a text token
279:                processTextBuffer();
280:
281:                if (TAPESTRY_SCHEMA_5_0_0.equals(uri)) {
282:                    startTapestryElement(qName, localName, attributes);
283:                    return;
284:                }
285:
286:                // TODO: Handle interpolations inside attributes?
287:
288:                startPossibleComponent(attributes, localName, null);
289:            }
290:
291:            /**
292:             * Checks to see if currently inside a t:body element (which should always be empty). Content is
293:             * ignored inside a body. If inside a body, then a warning is logged (but only one warning per
294:             * body element).
295:             * 
296:             * @return true if inside t:body, false otherwise
297:             */
298:            private boolean insideBody() {
299:                if (_insideBody) {
300:                    // Limit to one logged error per infraction.
301:
302:                    if (!_insideBodyErrorLogged)
303:                        _log
304:                                .error(ServicesMessages
305:                                        .contentInsideBodyNotAllowed(getCurrentLocation()));
306:
307:                    _insideBodyErrorLogged = true;
308:                }
309:
310:                return _insideBody;
311:            }
312:
313:            private void startTapestryElement(String qname, String localName,
314:                    Attributes attributes) {
315:                if (localName.equalsIgnoreCase("body")) {
316:                    startBody();
317:                    return;
318:                }
319:
320:                if (localName.equalsIgnoreCase("parameter")) {
321:                    startParameter(attributes);
322:                    return;
323:                }
324:
325:                if (localName.equalsIgnoreCase("block")) {
326:                    startBlock(attributes);
327:                    return;
328:                }
329:
330:                // The component type is derived from the element name. Since element names may not contain
331:                // slashes, we convert periods to slashes. Later down the pipeline, they'll probably be
332:                // converted back into periods, as part of a fully qualified class name.
333:
334:                String componentType = localName.replace('.', '/');
335:
336:                // With a component type specified, it's not just possibly a component ...
337:                startPossibleComponent(attributes, null, componentType);
338:            }
339:
340:            private void startBlock(Attributes attributes) {
341:                String blockId = findSingleParameter("block", "id", attributes);
342:
343:                // null is ok for blockId
344:
345:                _tokens.add(new BlockToken(blockId, getCurrentLocation()));
346:            }
347:
348:            private void startParameter(Attributes attributes) {
349:                String parameterName = findSingleParameter("parameter", "name",
350:                        attributes);
351:
352:                if (InternalUtils.isBlank(parameterName))
353:                    throw new TapestryException(ServicesMessages
354:                            .parameterElementNameRequired(),
355:                            getCurrentLocation(), null);
356:
357:                _tokens.add(new ParameterToken(parameterName,
358:                        getCurrentLocation()));
359:            }
360:
361:            private String findSingleParameter(String elementName,
362:                    String attributeName, Attributes attributes) {
363:                String result = null;
364:
365:                for (int i = 0; i < attributes.getLength(); i++) {
366:                    String name = attributes.getLocalName(i);
367:
368:                    if (name.equals(attributeName)) {
369:                        result = attributes.getValue(i);
370:                        continue;
371:                    }
372:
373:                    // Only the name attribute is allowed.
374:
375:                    throw new TapestryException(ServicesMessages
376:                            .undefinedTapestryAttribute(elementName, name,
377:                                    attributeName), getCurrentLocation(), null);
378:                }
379:
380:                return result;
381:            }
382:
383:            private String nullForBlank(String input) {
384:                return InternalUtils.isBlank(input) ? null : input;
385:            }
386:
387:            /**
388:             * @param attributes
389:             *            the attributes for the element
390:             * @param elementName
391:             *            the name of the element (to be assigned to the new token), may be null for a
392:             *            component in the Tapestry namespace
393:             * @param identifiedType
394:             *            the type of the element, usually null, but may be the component type derived from
395:             *            the element name (for an element in the Tapestry namespace)
396:             */
397:            private void startPossibleComponent(Attributes attributes,
398:                    String elementName, String identifiedType) {
399:                String id = null;
400:                String type = identifiedType;
401:                String mixins = null;
402:                int count = attributes.getLength();
403:                Location location = getCurrentLocation();
404:                List<TemplateToken> attributeTokens = newList();
405:
406:                for (int i = 0; i < count; i++) {
407:                    String name = attributes.getLocalName(i);
408:
409:                    // The name will be blank for an xmlns: attribute
410:
411:                    if (InternalUtils.isBlank(name))
412:                        continue;
413:
414:                    String uri = attributes.getURI(i);
415:
416:                    String value = attributes.getValue(i);
417:
418:                    if (TAPESTRY_SCHEMA_5_0_0.equals(uri)) {
419:                        if (name.equalsIgnoreCase(ID_ATTRIBUTE_NAME)) {
420:                            id = nullForBlank(value);
421:                            continue;
422:                        }
423:
424:                        if (type == null
425:                                && name.equalsIgnoreCase(TYPE_ATTRIBUTE_NAME)) {
426:                            type = nullForBlank(value);
427:                            continue;
428:                        }
429:
430:                        if (name.equalsIgnoreCase(MIXINS_ATTRIBUTE_NAME)) {
431:                            mixins = nullForBlank(value);
432:                            continue;
433:                        }
434:
435:                        // Anything else is the name of a Tapestry component parameter that is simply
436:                        // not part of the template's doctype for the element being instrumented.
437:                    }
438:
439:                    attributeTokens.add(new AttributeToken(name, value,
440:                            location));
441:                }
442:
443:                boolean isComponent = (id != null || type != null);
444:
445:                // If provided t:mixins but not t:id or t:type, then its not quite a component
446:
447:                if (mixins != null && !isComponent)
448:                    throw new TapestryException(ServicesMessages
449:                            .mixinsInvalidWithoutIdOrType(elementName),
450:                            location, null);
451:
452:                if (isComponent) {
453:                    _tokens.add(new StartComponentToken(elementName, id, type,
454:                            mixins, location));
455:                } else {
456:                    _tokens.add(new StartElementToken(elementName, location));
457:                }
458:
459:                _tokens.addAll(attributeTokens);
460:
461:                if (id != null)
462:                    _componentIds.add(id);
463:            }
464:
465:            private void startBody() {
466:                _tokens.add(new BodyToken(getCurrentLocation()));
467:
468:                _insideBody = true;
469:                _insideBodyErrorLogged = false;
470:            }
471:
472:            public void endElement(String uri, String localName, String qName)
473:                    throws SAXException {
474:                processTextBuffer();
475:
476:                // TODO: Handle tapestry namespace elements?
477:
478:                // Because XML tags are always balanced, we don't even need to know what element just closed
479:                // when we assemble things later.
480:
481:                if (!_insideBody)
482:                    _tokens.add(new EndElementToken(getCurrentLocation()));
483:
484:                _insideBody = false;
485:            }
486:
487:            private Location getCurrentLocation() {
488:                if (_locator == null)
489:                    return null;
490:
491:                return new LocationImpl(_templateResource, _locator
492:                        .getLineNumber(), _locator.getColumnNumber());
493:            }
494:
495:            public void comment(char[] ch, int start, int length)
496:                    throws SAXException {
497:                if (_ignoreEvents || insideBody())
498:                    return;
499:
500:                processTextBuffer();
501:
502:                // Remove excess whitespace. The Comment DOM node will add a leadig and trailing space.
503:
504:                String comment = new String(ch, start, length).trim();
505:
506:                // TODO: Perhaps comments need to be "aggregated" the same way we aggregate text and CDATA.
507:                // Hm. Probably not. Any whitespace between one comment and the next will become a
508:                // TextToken.
509:                // Unless we trim whitespace between consecutive comments ... and on down the rabbit hole.
510:                // Oops -- unless a single comment may be passed into this method as multiple calls
511:                // (have to check how multiline comments are handled).
512:                // Tests against Sun's built in parser does show that multiline comments are still
513:                // provided as a single call to comment(), so we're good for the meantime (until we find
514:                // out some parsers aren't so compliant).
515:
516:                _tokens.add(new CommentToken(comment, getCurrentLocation()));
517:            }
518:
519:            public void endCDATA() throws SAXException {
520:                // Add a token for any accumulated CDATA.
521:
522:                processTextBuffer();
523:
524:                // Again, CDATA doesn't nest, so we know we're back to ordinary markup.
525:
526:                _textIsCData = false;
527:            }
528:
529:            public void startCDATA() throws SAXException {
530:                if (_ignoreEvents || insideBody())
531:                    return;
532:
533:                processTextBuffer();
534:
535:                // Because CDATA doesn't mix with any other SAX/lexical events, we can simply turn on a flag
536:                // here and turn it off when we see the end.
537:
538:                _textIsCData = true;
539:            }
540:
541:            // Empty methods defined by the various interfaces.
542:
543:            public void endDTD() throws SAXException {
544:            }
545:
546:            public void endEntity(String name) throws SAXException {
547:            }
548:
549:            public void startDTD(String name, String publicId, String systemId)
550:                    throws SAXException {
551:                // notes:
552:                // 1) a DTD has to occur at the very start of a document. Since we don't start
553:                // recording characters until we hit the first element of a document (see
554:                // characters and startElement), there should be no text to process.
555:                // It's worth noting that the sax parser will puke if any of the following
556:                // occur:
557:                // 1) a doctype is encountered multiple times in the same document
558:                // 2) a doctype is encountered anywhere other than the very first item
559:                // in a document.
560:                // Hence, the assumption made in 1 should hold.
561:                // Since an exception is thrown for case #1 above, we can just add the DTDToken.
562:                // When we go to process the token (in PageLoaderProcessor), we can make sure
563:                // that the final page has only a single DTDToken (the first one).
564:                _tokens.add(new DTDToken(name, publicId, systemId,
565:                        getCurrentLocation()));
566:            }
567:
568:            public void startEntity(String name) throws SAXException {
569:            }
570:
571:            public void endDocument() throws SAXException {
572:            }
573:
574:            public void endPrefixMapping(String prefix) throws SAXException {
575:            }
576:
577:            public void ignorableWhitespace(char[] ch, int start, int length)
578:                    throws SAXException {
579:            }
580:
581:            public void processingInstruction(String target, String data)
582:                    throws SAXException {
583:            }
584:
585:            public void skippedEntity(String name) throws SAXException {
586:            }
587:
588:            public void startDocument() throws SAXException {
589:            }
590:
591:            public void startPrefixMapping(String prefix, String uri)
592:                    throws SAXException {
593:            }
594:
595:            public InputSource resolveEntity(String publicId, String systemId)
596:                    throws SAXException, IOException {
597:                URL url = _configuration.get(publicId);
598:
599:                if (url != null)
600:                    return new InputSource(url.openStream());
601:
602:                return null;
603:            }
604:
605:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.