    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    }