    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            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(String.format("Attribute expression '%s' is missing a closing brace.", expression), location, null);
    111
    112            String expansion = expression.substring(expansionx + 2, endx);
    113
    114            final Binding binding = bindingSource.newBinding("attribute expansion", resources, resources,
    115                    BindingConstants.PROP, expansion, location);
    116
    117            final StringProvider provider = new StringProvider()
    118            {
    119                public String provideString()
    120                {
    121                    try
    122                    {
    123                        Object raw = binding.get();
    124
    125                        return typeCoercer.coerce(raw, String.class);
    126                    } catch (Exception ex)
    127                    {
    128                        throw new TapestryException(ex.getMessage(), location, ex);
    129                    }
    130                }
    131            };
    132
    133            providers.add(provider);
    134
    135            // Restart the search after '}'
    136
    137            startx = endx + 1;
    138        }
    139
    140        // Simplify the typical case, where the entire attribute is just a single expansion:
    141
    142        if (providers.size() == 1) return providers.get(0);
    143
    144        return new StringProvider()
    145        {
    146
    147            public String provideString()
    148            {
    149                StringBuilder builder = new StringBuilder();
    150
    151                for (StringProvider provider : providers)
    152                    builder.append(provider.provideString());
    153
    154                return builder.toString();
    155            }
    156        };
    157    }
    158
    159    public RenderCommand newExpansionElement(ComponentResources componentResources, ExpansionToken token)
    160    {
    161        Binding binding = bindingSource.newBinding("expansion", componentResources, componentResources,
    162                BindingConstants.PROP, token.getExpression(), token.getLocation());
    163
    164        return new ExpansionPageElement(binding, typeCoercer);
    165    }
    166
    167    public Binding newBinding(String parameterName, ComponentResources loadingComponentResources,
    168                              ComponentResources embeddedComponentResources, String defaultBindingPrefix,
    169                              String expression, Location location)
    170    {
    171
    172        if (expression.contains(InternalConstants.EXPANSION_START))
    173        {
    174            StringProvider provider = parseAttributeExpansionExpression(expression, loadingComponentResources,
    175                    location);
    176
    177            return new AttributeExpansionBinding(location, provider);
    178        }
    179
    180        return bindingSource.newBinding(parameterName, loadingComponentResources,
    181                embeddedComponentResources, defaultBindingPrefix, expression, location);
    182    }
    183}