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.TapestryConstants.PROP_BINDING_PREFIX;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
019:
020: import java.util.List;
021:
022: import org.apache.tapestry.Binding;
023: import org.apache.tapestry.ComponentResources;
024: import org.apache.tapestry.MarkupWriter;
025: import org.apache.tapestry.internal.parser.AttributeToken;
026: import org.apache.tapestry.internal.parser.CommentToken;
027: import org.apache.tapestry.internal.parser.DTDToken;
028: import org.apache.tapestry.internal.parser.ExpansionToken;
029: import org.apache.tapestry.internal.parser.StartElementToken;
030: import org.apache.tapestry.internal.parser.TextToken;
031: import org.apache.tapestry.internal.structure.CommentPageElement;
032: import org.apache.tapestry.internal.structure.ComponentPageElement;
033: import org.apache.tapestry.internal.structure.ComponentPageElementImpl;
034: import org.apache.tapestry.internal.structure.DTDPageElement;
035: import org.apache.tapestry.internal.structure.ExpansionPageElement;
036: import org.apache.tapestry.internal.structure.Page;
037: import org.apache.tapestry.internal.structure.PageElement;
038: import org.apache.tapestry.internal.structure.StartElementPageElement;
039: import org.apache.tapestry.internal.structure.TextPageElement;
040: import org.apache.tapestry.ioc.Location;
041: import org.apache.tapestry.ioc.internal.util.InternalUtils;
042: import org.apache.tapestry.ioc.internal.util.TapestryException;
043: import org.apache.tapestry.ioc.services.TypeCoercer;
044: import org.apache.tapestry.model.ComponentModel;
045: import org.apache.tapestry.runtime.RenderQueue;
046: import org.apache.tapestry.services.BindingSource;
047: import org.apache.tapestry.services.ComponentClassResolver;
048: import org.apache.tapestry.services.ComponentMessagesSource;
049:
050: public class PageElementFactoryImpl implements PageElementFactory {
051: private final ComponentInstantiatorSource _componentInstantiatorSource;
052:
053: private final ComponentClassResolver _componentClassResolver;
054:
055: private final TypeCoercer _typeCoercer;
056:
057: private final BindingSource _bindingSource;
058:
059: private final ComponentMessagesSource _messagesSource;
060:
061: private static final String EXPANSION_START = "${";
062:
063: private static class LiteralStringProvider implements
064: StringProvider {
065: private final String _string;
066:
067: LiteralStringProvider(String string) {
068: _string = string;
069: }
070:
071: public String provideString() {
072: return _string;
073: }
074: }
075:
076: public PageElementFactoryImpl(
077: ComponentInstantiatorSource componentInstantiatorSource,
078: ComponentClassResolver resolver, TypeCoercer typeCoercer,
079: BindingSource bindingSource,
080: ComponentMessagesSource messagesSource) {
081: _componentInstantiatorSource = componentInstantiatorSource;
082: _componentClassResolver = resolver;
083: _typeCoercer = typeCoercer;
084: _bindingSource = bindingSource;
085: _messagesSource = messagesSource;
086: }
087:
088: /** Singleton instance that represents any close tag of any element in any template. */
089: private final PageElement _endElement = new PageElement() {
090: public void render(MarkupWriter writer, RenderQueue queue) {
091: writer.end();
092: }
093:
094: @Override
095: public String toString() {
096: return "End";
097: }
098: };
099:
100: public PageElement newStartElement(StartElementToken token) {
101: return new StartElementPageElement(token.getName());
102: }
103:
104: public PageElement newTextElement(TextToken token) {
105: return new TextPageElement(token.getText());
106: }
107:
108: public PageElement newEndElement() {
109: return _endElement;
110: }
111:
112: public PageElement newAttributeElement(
113: ComponentResources componentResources,
114: final AttributeToken token) {
115: final StringProvider provider = parseAttributeExpansionExpression(
116: token.getValue(), componentResources, token
117: .getLocation());
118:
119: final String name = token.getName();
120:
121: return new PageElement() {
122: public void render(MarkupWriter writer, RenderQueue queue) {
123: writer.attributes(name, provider.provideString());
124: }
125: };
126: }
127:
128: private StringProvider parseAttributeExpansionExpression(
129: String expression, ComponentResources resources,
130: final Location location) {
131: final List<StringProvider> providers = newList();
132:
133: int startx = 0;
134:
135: while (true) {
136: int expansionx = expression
137: .indexOf(EXPANSION_START, startx);
138:
139: // No more expansions, add in the rest of the string as a literal.
140:
141: if (expansionx < 0) {
142: if (startx < expression.length())
143: providers.add(new LiteralStringProvider(expression
144: .substring(startx)));
145: break;
146: }
147:
148: // Add in a literal string chunk for the characters between the last expansion and
149: // this expansion.
150:
151: if (startx != expansionx)
152: providers.add(new LiteralStringProvider(expression
153: .substring(startx, expansionx)));
154:
155: int endx = expression.indexOf("}", expansionx);
156:
157: if (endx < 0)
158: throw new TapestryException(ServicesMessages
159: .unclosedAttributeExpression(expression),
160: location, null);
161:
162: String expansion = expression.substring(expansionx + 2,
163: endx);
164:
165: final Binding binding = _bindingSource.newBinding(
166: "attribute expansion", resources, resources,
167: PROP_BINDING_PREFIX, expansion, location);
168:
169: final StringProvider provider = new StringProvider() {
170: public String provideString() {
171: try {
172: Object raw = binding.get();
173:
174: return _typeCoercer.coerce(raw, String.class);
175: } catch (Exception ex) {
176: throw new TapestryException(ex.getMessage(),
177: location, ex);
178: }
179: }
180: };
181:
182: providers.add(provider);
183:
184: // Restart the search after '}'
185:
186: startx = endx + 1;
187: }
188:
189: // Simplify the typical case, where the entire attribute is just a single expansion:
190:
191: if (providers.size() == 1)
192: return providers.get(0);
193:
194: return new StringProvider() {
195:
196: public String provideString() {
197: StringBuilder builder = new StringBuilder();
198:
199: for (StringProvider provider : providers)
200: builder.append(provider.provideString());
201:
202: return builder.toString();
203: }
204: };
205:
206: }
207:
208: public PageElement newExpansionElement(
209: ComponentResources componentResources, ExpansionToken token) {
210: Binding binding = _bindingSource.newBinding("expansion",
211: componentResources, componentResources,
212: PROP_BINDING_PREFIX, token.getExpression(), token
213: .getLocation());
214:
215: return new ExpansionPageElement(binding, _typeCoercer);
216: }
217:
218: public ComponentPageElement newComponentElement(Page page,
219: ComponentPageElement container, String id,
220: String componentType, String componentClassName,
221: String elementName, Location location) {
222: try {
223: String finalClassName = componentClassName;
224:
225: // This awkwardness is making me think that the page loader should resolve the component
226: // type before invoking this method (we would then remove the componentType parameter).
227:
228: if (InternalUtils.isNonBlank(componentType)) {
229: // The type actually overrides the specified class name. The class name is defined
230: // by the type of the field. In many scenarios, the field type is a common
231: // interface,
232: // and the type is used to determine the concrete class to instantiate.
233:
234: try {
235: finalClassName = _componentClassResolver
236: .resolveComponentTypeToClassName(componentType);
237: } catch (IllegalArgumentException ex) {
238: throw new TapestryException(ex.getMessage(),
239: location, ex);
240: }
241: }
242:
243: Instantiator instantiator = _componentInstantiatorSource
244: .findInstantiator(finalClassName);
245:
246: // This is actually a good place to check for recursive templates, here where we've
247: // resolved
248: // the component type to a fully qualified class name.
249:
250: checkForRecursion(finalClassName, container, location);
251:
252: // The container for any components is the loading component, regardless of
253: // how the component elements are nested within the loading component's
254: // template.
255:
256: ComponentPageElementImpl result = new ComponentPageElementImpl(
257: page, container, id, elementName, instantiator,
258: _typeCoercer, _messagesSource, location);
259:
260: page.addLifecycleListener(result);
261:
262: container.addEmbeddedElement(result);
263:
264: addMixins(result, instantiator);
265:
266: return result;
267: } catch (RuntimeException ex) {
268: throw new TapestryException(ex.getMessage(), location, ex);
269: }
270: }
271:
272: private void checkForRecursion(String componentClassName,
273: ComponentPageElement container, Location location) {
274: // Container may be null for a root element;
275:
276: if (container == null)
277: return;
278:
279: ComponentResources resources = container
280: .getComponentResources();
281:
282: while (resources != null) {
283: if (resources.getComponentModel().getComponentClassName()
284: .equals(componentClassName))
285: throw new TapestryException(ServicesMessages
286: .componentRecursion(componentClassName),
287: location, null);
288:
289: resources = resources.getContainerResources();
290: }
291: }
292:
293: public ComponentPageElement newRootComponentElement(Page page,
294: String componentType) {
295: Instantiator instantiator = _componentInstantiatorSource
296: .findInstantiator(componentType);
297:
298: ComponentPageElementImpl result = new ComponentPageElementImpl(
299: page, instantiator, _typeCoercer, _messagesSource);
300:
301: addMixins(result, instantiator);
302:
303: page.addLifecycleListener(result);
304:
305: return result;
306: }
307:
308: private void addMixins(ComponentPageElement component,
309: Instantiator instantiator) {
310: ComponentModel model = instantiator.getModel();
311: for (String mixinClassName : model.getMixinClassNames())
312: addMixinByClassName(component, mixinClassName);
313: }
314:
315: public PageElement newRenderBodyElement(
316: final ComponentPageElement component) {
317: return new PageElement() {
318: public void render(MarkupWriter writer, RenderQueue queue) {
319: component.enqueueBeforeRenderBody(queue);
320: }
321:
322: @Override
323: public String toString() {
324: return String.format("RenderBody[%s]", component
325: .getNestedId());
326: }
327: };
328: }
329:
330: public void addMixinByTypeName(ComponentPageElement component,
331: String mixinType) {
332: String mixinClassName = _componentClassResolver
333: .resolveMixinTypeToClassName(mixinType);
334:
335: addMixinByClassName(component, mixinClassName);
336: }
337:
338: public void addMixinByClassName(ComponentPageElement component,
339: String mixinClassName) {
340: Instantiator mixinInstantiator = _componentInstantiatorSource
341: .findInstantiator(mixinClassName);
342:
343: component.addMixin(mixinInstantiator);
344: }
345:
346: public PageElement newCommentElement(CommentToken token) {
347: return new CommentPageElement(token.getComment());
348: }
349:
350: public PageElement newDTDElement(DTDToken token) {
351: return new DTDPageElement(token.getName(), token.getPublicId(),
352: token.getSystemId());
353: }
354:
355: public Binding newBinding(String parameterName,
356: ComponentResources loadingComponentResources,
357: ComponentResources embeddedComponentResources,
358: String defaultBindingPrefix, String expression,
359: Location location) {
360:
361: if (expression.contains(EXPANSION_START)) {
362: StringProvider provider = parseAttributeExpansionExpression(
363: expression, loadingComponentResources, location);
364:
365: return new AttributeExpansionBinding(provider, location);
366: }
367:
368: return _bindingSource.newBinding("parameter " + parameterName,
369: loadingComponentResources, embeddedComponentResources,
370: defaultBindingPrefix, expression, location);
371: }
372: }
|