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