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}