001    // Copyright 2006, 2007, 2008, 2009 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.tapestry5.internal.services;
016    
017    import org.apache.tapestry5.Binding;
018    import org.apache.tapestry5.BindingConstants;
019    import org.apache.tapestry5.ComponentResources;
020    import org.apache.tapestry5.MarkupWriter;
021    import org.apache.tapestry5.internal.InternalConstants;
022    import org.apache.tapestry5.internal.parser.AttributeToken;
023    import org.apache.tapestry5.internal.parser.ExpansionToken;
024    import org.apache.tapestry5.internal.structure.ExpansionPageElement;
025    import org.apache.tapestry5.ioc.Location;
026    import org.apache.tapestry5.ioc.internal.util.TapestryException;
027    import org.apache.tapestry5.ioc.services.TypeCoercer;
028    import org.apache.tapestry5.runtime.RenderCommand;
029    import org.apache.tapestry5.runtime.RenderQueue;
030    import org.apache.tapestry5.services.BindingSource;
031    
032    import java.util.List;
033    
034    import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
035    
036    public class PageElementFactoryImpl implements PageElementFactory
037    {
038        private final TypeCoercer typeCoercer;
039    
040        private final BindingSource bindingSource;
041    
042        private static class LiteralStringProvider implements StringProvider
043        {
044            private final String string;
045    
046            LiteralStringProvider(String string)
047            {
048                this.string = string;
049            }
050    
051            public String provideString()
052            {
053                return string;
054            }
055        }
056    
057        public PageElementFactoryImpl(TypeCoercer typeCoercer, BindingSource bindingSource)
058        {
059            this.typeCoercer = typeCoercer;
060            this.bindingSource = bindingSource;
061        }
062    
063        public RenderCommand newAttributeElement(ComponentResources componentResources, final AttributeToken token)
064        {
065            final StringProvider provider = parseAttributeExpansionExpression(token.value, componentResources,
066                    token.getLocation());
067    
068            return new RenderCommand()
069            {
070                public void render(MarkupWriter writer, RenderQueue queue)
071                {
072                    writer.attributeNS(token.namespaceURI, token.name, provider.provideString());
073                }
074    
075                public String toString()
076                {
077                    return String.format("AttributeNS[%s %s \"%s\"]", token.namespaceURI, token.name, token.value);
078                }
079            };
080        }
081    
082        private StringProvider parseAttributeExpansionExpression(String expression, ComponentResources resources,
083                                                                 final Location location)
084        {
085            final List<StringProvider> providers = newList();
086    
087            int startx = 0;
088    
089            while (true)
090            {
091                int expansionx = expression.indexOf(InternalConstants.EXPANSION_START, startx);
092    
093                // No more expansions, add in the rest of the string as a literal.
094    
095                if (expansionx < 0)
096                {
097                    if (startx < expression.length())
098                        providers.add(new LiteralStringProvider(expression.substring(startx)));
099                    break;
100                }
101    
102                // Add in a literal string chunk for the characters between the last expansion and
103                // this expansion.
104    
105                if (startx != expansionx)
106                    providers.add(new LiteralStringProvider(expression.substring(startx, expansionx)));
107    
108                int endx = expression.indexOf("}", expansionx);
109    
110                if (endx < 0) throw new TapestryException(ServicesMessages
111                        .unclosedAttributeExpression(expression), location, null);
112    
113                String expansion = expression.substring(expansionx + 2, endx);
114    
115                final Binding binding = bindingSource.newBinding("attribute expansion", resources, resources,
116                        BindingConstants.PROP, expansion, location);
117    
118                final StringProvider provider = new StringProvider()
119                {
120                    public String provideString()
121                    {
122                        try
123                        {
124                            Object raw = binding.get();
125    
126                            return typeCoercer.coerce(raw, String.class);
127                        } catch (Exception ex)
128                        {
129                            throw new TapestryException(ex.getMessage(), location, ex);
130                        }
131                    }
132                };
133    
134                providers.add(provider);
135    
136                // Restart the search after '}'
137    
138                startx = endx + 1;
139            }
140    
141            // Simplify the typical case, where the entire attribute is just a single expansion:
142    
143            if (providers.size() == 1) return providers.get(0);
144    
145            return new StringProvider()
146            {
147    
148                public String provideString()
149                {
150                    StringBuilder builder = new StringBuilder();
151    
152                    for (StringProvider provider : providers)
153                        builder.append(provider.provideString());
154    
155                    return builder.toString();
156                }
157            };
158        }
159    
160        public RenderCommand newExpansionElement(ComponentResources componentResources, ExpansionToken token)
161        {
162            Binding binding = bindingSource.newBinding("expansion", componentResources, componentResources,
163                    BindingConstants.PROP, token.getExpression(), token.getLocation());
164    
165            return new ExpansionPageElement(binding, typeCoercer);
166        }
167    
168        public Binding newBinding(String parameterName, ComponentResources loadingComponentResources,
169                                  ComponentResources embeddedComponentResources, String defaultBindingPrefix,
170                                  String expression, Location location)
171        {
172    
173            if (expression.contains(InternalConstants.EXPANSION_START))
174            {
175                StringProvider provider = parseAttributeExpansionExpression(expression, loadingComponentResources,
176                        location);
177    
178                return new AttributeExpansionBinding(location, provider);
179            }
180    
181            return bindingSource.newBinding(parameterName, loadingComponentResources,
182                    embeddedComponentResources, defaultBindingPrefix, expression, location);
183        }
184    }