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}