    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}