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
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.internal.InternalConstants;
022import org.apache.tapestry5.internal.parser.AttributeToken;
023import org.apache.tapestry5.internal.parser.ExpansionToken;
024import org.apache.tapestry5.internal.structure.ExpansionPageElement;
025import org.apache.tapestry5.ioc.Location;
026import org.apache.tapestry5.ioc.internal.util.TapestryException;
027import org.apache.tapestry5.ioc.services.TypeCoercer;
028import org.apache.tapestry5.runtime.RenderCommand;
029import org.apache.tapestry5.runtime.RenderQueue;
030import org.apache.tapestry5.services.BindingSource;
031
032import java.util.List;
033
034import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
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            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}