Clover coverage report - Code Coverage for tapestry release 4.0.1
Coverage timestamp: Fri Mar 31 2006 09:12:14 EST
file stats: LOC: 437   Methods: 15
NCLOC: 265   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ComponentTemplateLoaderLogic.java 83.3% 89.3% 93.3% 87.7%
coverage coverage
 1    // Copyright 2004, 2005 The Apache Software Foundation
 2    //
 3    // Licensed under the Apache License, Version 2.0 (the "License");
 4    // you may not use this file except in compliance with the License.
 5    // You may obtain a copy of the License at
 6    //
 7    // http://www.apache.org/licenses/LICENSE-2.0
 8    //
 9    // Unless required by applicable law or agreed to in writing, software
 10    // distributed under the License is distributed on an "AS IS" BASIS,
 11    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    // See the License for the specific language governing permissions and
 13    // limitations under the License.
 14   
 15    package org.apache.tapestry.services.impl;
 16   
 17    import java.util.HashSet;
 18    import java.util.Iterator;
 19    import java.util.Map;
 20    import java.util.Set;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.hivemind.ApplicationRuntimeException;
 24    import org.apache.hivemind.Location;
 25    import org.apache.tapestry.IBinding;
 26    import org.apache.tapestry.IComponent;
 27    import org.apache.tapestry.IRender;
 28    import org.apache.tapestry.IRequestCycle;
 29    import org.apache.tapestry.ITemplateComponent;
 30    import org.apache.tapestry.Tapestry;
 31    import org.apache.tapestry.binding.BindingConstants;
 32    import org.apache.tapestry.binding.BindingSource;
 33    import org.apache.tapestry.binding.LiteralBinding;
 34    import org.apache.tapestry.engine.IPageLoader;
 35    import org.apache.tapestry.parse.CloseToken;
 36    import org.apache.tapestry.parse.ComponentTemplate;
 37    import org.apache.tapestry.parse.LocalizationToken;
 38    import org.apache.tapestry.parse.OpenToken;
 39    import org.apache.tapestry.parse.TemplateToken;
 40    import org.apache.tapestry.parse.TextToken;
 41    import org.apache.tapestry.parse.TokenType;
 42    import org.apache.tapestry.services.TemplateSource;
 43    import org.apache.tapestry.spec.IComponentSpecification;
 44    import org.apache.tapestry.spec.IContainedComponent;
 45    import org.apache.tapestry.spec.IParameterSpecification;
 46   
 47    /**
 48    * Contains the logic from {@link org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl},
 49    * which creates one of these instances to process the request. This is necessary because the
 50    * service must be re-entrant (because templates can contain components that have templates).
 51    *
 52    * @author Howard Lewis Ship
 53    * @since 4.0
 54    */
 55    public class ComponentTemplateLoaderLogic
 56    {
 57    private Log _log;
 58   
 59    private IPageLoader _pageLoader;
 60   
 61    private IRequestCycle _requestCycle;
 62   
 63    private ITemplateComponent _loadComponent;
 64   
 65    private BindingSource _bindingSource;
 66   
 67    private IComponent[] _stack;
 68   
 69    private int _stackx;
 70   
 71    private IComponent _activeComponent = null;
 72   
 73    private Set _seenIds = new HashSet();
 74   
 75  392 public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource)
 76    {
 77  392 _log = log;
 78  392 _pageLoader = pageLoader;
 79  392 _bindingSource = bindingSource;
 80    }
 81   
 82  392 public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent,
 83    ComponentTemplate template)
 84    {
 85  392 _requestCycle = requestCycle;
 86  392 _loadComponent = loadComponent;
 87   
 88  392 process(template);
 89    }
 90   
 91  392 private void process(ComponentTemplate template)
 92    {
 93  392 int count = template.getTokenCount();
 94   
 95  392 _stack = new IComponent[count];
 96   
 97  392 for (int i = 0; i < count; i++)
 98    {
 99  6340 TemplateToken token = template.getToken(i);
 100   
 101  6340 TokenType type = token.getType();
 102   
 103  6340 if (type == TokenType.TEXT)
 104    {
 105  2796 process((TextToken) token);
 106  2796 continue;
 107    }
 108   
 109  3544 if (type == TokenType.OPEN)
 110    {
 111  1770 process((OpenToken) token);
 112  1756 continue;
 113    }
 114   
 115  1774 if (type == TokenType.CLOSE)
 116    {
 117  1746 process((CloseToken) token);
 118  1746 continue;
 119    }
 120   
 121  28 if (type == TokenType.LOCALIZATION)
 122    {
 123  28 process((LocalizationToken) token);
 124  28 continue;
 125    }
 126    }
 127   
 128    // This is also pretty much unreachable, and the message is kind of out
 129    // of date, too.
 130   
 131  378 if (_stackx != 0)
 132  0 throw new ApplicationRuntimeException(Tapestry
 133    .getMessage("BaseComponent.unbalance-open-tags"), _loadComponent, null, null);
 134   
 135  378 checkAllComponentsReferenced();
 136    }
 137   
 138    /**
 139    * Adds the token (which implements {@link IRender}) to the active component (using
 140    * {@link IComponent#addBody(IRender)}), or to this component
 141    * {@link BaseComponent#addOuter(IRender)}.
 142    * <p>
 143    * A check is made that the active component allows a body.
 144    */
 145   
 146  2796 private void process(TextToken token)
 147    {
 148  2796 if (_activeComponent == null)
 149    {
 150  812 _loadComponent.addOuter(token);
 151  812 return;
 152    }
 153   
 154  1984 if (!_activeComponent.getSpecification().getAllowBody())
 155  0 throw createBodylessComponentException(_activeComponent);
 156   
 157  1984 _activeComponent.addBody(token);
 158    }
 159   
 160  1770 private void process(OpenToken token)
 161    {
 162  1770 String id = token.getId();
 163  1770 IComponent component = null;
 164  1770 String componentType = token.getComponentType();
 165   
 166  1770 if (componentType == null)
 167  652 component = getEmbeddedComponent(id);
 168    else
 169    {
 170  1118 checkForDuplicateId(id, token.getLocation());
 171   
 172  1116 component = createImplicitComponent(id, componentType, token.getLocation());
 173    }
 174   
 175    // Make sure the template contains each component only once.
 176   
 177  1762 if (_seenIds.contains(id))
 178  0 throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences(
 179    _loadComponent,
 180    id), _loadComponent, token.getLocation(), null);
 181   
 182  1762 _seenIds.add(id);
 183   
 184  1762 if (_activeComponent == null)
 185  626 _loadComponent.addOuter(component);
 186    else
 187    {
 188    // Note: this code may no longer be reachable (because the
 189    // template parser does this check first).
 190   
 191  1136 if (!_activeComponent.getSpecification().getAllowBody())
 192  0 throw createBodylessComponentException(_activeComponent);
 193   
 194  1136 _activeComponent.addBody(component);
 195    }
 196   
 197  1762 addTemplateBindings(component, token);
 198   
 199  1756 _stack[_stackx++] = _activeComponent;
 200   
 201  1756 _activeComponent = component;
 202    }
 203   
 204  1118 private void checkForDuplicateId(String id, Location location)
 205    {
 206  1118 if (id == null)
 207  0 return;
 208   
 209  1118 IContainedComponent cc = _loadComponent.getSpecification().getComponent(id);
 210   
 211  1118 if (cc != null)
 212  2 throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc),
 213    _loadComponent, location, null);
 214    }
 215   
 216  1116 private IComponent createImplicitComponent(String id, String componentType, Location location)
 217    {
 218  1116 IComponent result = _pageLoader.createImplicitComponent(
 219    _requestCycle,
 220    _loadComponent,
 221    id,
 222    componentType,
 223    location);
 224   
 225  1110 return result;
 226    }
 227   
 228  652 private IComponent getEmbeddedComponent(String id)
 229    {
 230  652 return _loadComponent.getComponent(id);
 231    }
 232   
 233  1746 private void process(CloseToken token)
 234    {
 235    // Again, this is pretty much impossible to reach because
 236    // the template parser does a great job.
 237   
 238  1746 if (_stackx <= 0)
 239  0 throw new ApplicationRuntimeException(ImplMessages.unbalancedCloseTags(),
 240    _loadComponent, token.getLocation(), null);
 241   
 242    // Null and forget the top element on the stack.
 243   
 244  1746 _stack[_stackx--] = null;
 245   
 246  1746 _activeComponent = _stack[_stackx];
 247    }
 248   
 249  28 private void process(LocalizationToken token)
 250    {
 251  28 IRender render = new LocalizedStringRender(_loadComponent, token);
 252   
 253  28 if (_activeComponent == null)
 254  14 _loadComponent.addOuter(render);
 255    else
 256  14 _activeComponent.addBody(render);
 257    }
 258   
 259    /**
 260    * Adds bindings based on attributes in the template.
 261    */
 262   
 263  1762 void addTemplateBindings(IComponent component, OpenToken token)
 264    {
 265  1762 IComponentSpecification spec = component.getSpecification();
 266   
 267  1762 Map attributes = token.getAttributesMap();
 268   
 269  1762 if (attributes != null)
 270    {
 271  982 Iterator i = attributes.entrySet().iterator();
 272   
 273  982 while (i.hasNext())
 274    {
 275  1314 Map.Entry entry = (Map.Entry) i.next();
 276   
 277  1314 String attributeName = (String) entry.getKey();
 278  1314 String value = (String) entry.getValue();
 279   
 280  1314 IParameterSpecification pspec = spec.getParameter(attributeName);
 281  1314 String parameterName = pspec == null ? attributeName : pspec.getParameterName();
 282   
 283  1314 if (!attributeName.equals(parameterName))
 284  0 _log.warn(ImplMessages.usedTemplateParameterAlias(
 285    token,
 286    attributeName,
 287    parameterName));
 288   
 289  1314 String description = ImplMessages.templateParameterName(parameterName);
 290   
 291    // Values in a template are always literal, unless prefixed.
 292   
 293  1314 IBinding binding = _bindingSource.createBinding(
 294    _loadComponent,
 295    description,
 296    value,
 297    BindingConstants.LITERAL_PREFIX,
 298    token.getLocation());
 299   
 300  1314 addBinding(component, spec, parameterName, binding);
 301    }
 302    }
 303   
 304    // if the component defines a templateTag parameter and
 305    // there is no established binding for that parameter,
 306    // add a static binding carrying the template tag
 307   
 308  1756 if (spec.getParameter(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null
 309    && component.getBinding(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null)
 310    {
 311  12 IBinding binding = _bindingSource.createBinding(
 312    component,
 313    TemplateSource.TEMPLATE_TAG_PARAMETER_NAME,
 314    token.getTag(),
 315    BindingConstants.LITERAL_PREFIX,
 316    token.getLocation());
 317   
 318  12 addBinding(component, spec, TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, binding);
 319    }
 320    }
 321   
 322    /**
 323    * Adds an expression binding, checking for errors related to reserved and informal parameters.
 324    * <p>
 325    * It is an error to specify expression bindings in both the specification and the template.
 326    */
 327   
 328  1326 private void addBinding(IComponent component, IComponentSpecification spec, String name,
 329    IBinding binding)
 330    {
 331   
 332    // If matches a formal parameter name, allow it to be set
 333    // unless there's already a binding.
 334   
 335  1326 boolean valid = validate(component, spec, name, binding);
 336   
 337  1320 if (valid)
 338  1270 component.setBinding(name, binding);
 339    }
 340   
 341  1326 private boolean validate(IComponent component, IComponentSpecification spec, String name,
 342    IBinding binding)
 343    {
 344    // TODO: This is ugly! Need a better/smarter way, even if we have to extend BindingSource
 345    // to tell us.
 346   
 347  1326 boolean isLiteral = binding instanceof LiteralBinding;
 348  1326 boolean isBound = component.getBinding(name) != null;
 349  1326 boolean isFormal = spec.getParameter(name) != null;
 350   
 351  1326 if (!isFormal)
 352    {
 353  112 if (!spec.getAllowInformalParameters())
 354    {
 355    // Again; if informal parameters are disallowed, ignore literal bindings, as they
 356    // are there as placeholders or for WYSIWYG.
 357   
 358  2 if (isLiteral)
 359  0 return false;
 360   
 361  2 throw new ApplicationRuntimeException(ImplMessages
 362    .templateBindingForInformalParameter(_loadComponent, name, component),
 363    component, binding.getLocation(), null);
 364    }
 365   
 366    // If the name is reserved (matches a formal parameter
 367    // or reserved name, caselessly), then skip it.
 368   
 369  110 if (spec.isReservedParameterName(name))
 370    {
 371    // Final case for literals: if they conflict with a reserved name, they are ignored.
 372    // Again, there for WYSIWYG.
 373   
 374  50 if (isLiteral)
 375  48 return false;
 376   
 377  2 throw new ApplicationRuntimeException(ImplMessages
 378    .templateBindingForReservedParameter(_loadComponent, name, component),
 379    component, binding.getLocation(), null);
 380    }
 381    }
 382   
 383    // So, at this point it doesn't matter if the parameter is a formal parameter or
 384    // an informal parameter. The binding (if any) in the specification takes precendence
 385    // over the template. Literal bindings that conflict are considered to be there for WYSIWYG
 386    // purposes. Non-literal bindings that conflict with a specification binding are an
 387    // error.
 388   
 389  1274 if (isBound)
 390    {
 391    // Literal bindings in the template that conflict with bound parameters
 392    // from the spec are silently ignored.
 393   
 394  4 if (isLiteral)
 395  2 return false;
 396   
 397  2 throw new ApplicationRuntimeException(ImplMessages.dupeTemplateBinding(
 398    name,
 399    component,
 400    _loadComponent), component, binding.getLocation(), null);
 401    }
 402   
 403  1270 return true;
 404   
 405    }
 406   
 407  378 private void checkAllComponentsReferenced()
 408    {
 409    // First, contruct a modifiable copy of the ids of all expected components
 410    // (that is, components declared in the specification).
 411   
 412  378 Map components = _loadComponent.getComponents();
 413   
 414  378 Set ids = components.keySet();
 415   
 416    // If the seen ids ... ids referenced in the template, matches
 417    // all the ids in the specification then we're fine.
 418   
 419  378 if (_seenIds.containsAll(ids))
 420  378 return;
 421   
 422    // Create a modifiable copy. Remove the ids that are referenced in
 423    // the template. The remainder are worthy of note.
 424   
 425  0 ids = new HashSet(ids);
 426  0 ids.removeAll(_seenIds);
 427   
 428  0 _log.warn(ImplMessages.missingComponentSpec(_loadComponent, ids));
 429   
 430    }
 431   
 432  0 private ApplicationRuntimeException createBodylessComponentException(IComponent component)
 433    {
 434  0 return new ApplicationRuntimeException(ImplMessages.bodylessComponent(), component, null,
 435    null);
 436    }
 437    }