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