Clover coverage report - Code Coverage for tapestry release 4.0-beta-10
Coverage timestamp: Sat Oct 8 2005 19:08:05 EDT
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  206 public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource)
 76    {
 77  206 _log = log;
 78  206 _pageLoader = pageLoader;
 79  206 _bindingSource = bindingSource;
 80    }
 81   
 82  206 public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent,
 83    ComponentTemplate template)
 84    {
 85  206 _requestCycle = requestCycle;
 86  206 _loadComponent = loadComponent;
 87   
 88  206 process(template);
 89    }
 90   
 91  206 private void process(ComponentTemplate template)
 92    {
 93  206 int count = template.getTokenCount();
 94   
 95  206 _stack = new IComponent[count];
 96   
 97  206 for (int i = 0; i < count; i++)
 98    {
 99  3343 TemplateToken token = template.getToken(i);
 100   
 101  3343 TokenType type = token.getType();
 102   
 103  3343 if (type == TokenType.TEXT)
 104    {
 105  1479 process((TextToken) token);
 106  1479 continue;
 107    }
 108   
 109  1864 if (type == TokenType.OPEN)
 110    {
 111  931 process((OpenToken) token);
 112  924 continue;
 113    }
 114   
 115  933 if (type == TokenType.CLOSE)
 116    {
 117  919 process((CloseToken) token);
 118  919 continue;
 119    }
 120   
 121  14 if (type == TokenType.LOCALIZATION)
 122    {
 123  14 process((LocalizationToken) token);
 124  14 continue;
 125    }
 126    }
 127   
 128    // This is also pretty much unreachable, and the message is kind of out
 129    // of date, too.
 130   
 131  199 if (_stackx != 0)
 132  0 throw new ApplicationRuntimeException(Tapestry
 133    .getMessage("BaseComponent.unbalance-open-tags"), _loadComponent, null, null);
 134   
 135  199 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  1479 private void process(TextToken token)
 147    {
 148  1479 if (_activeComponent == null)
 149    {
 150  409 _loadComponent.addOuter(token);
 151  409 return;
 152    }
 153   
 154  1070 if (!_activeComponent.getSpecification().getAllowBody())
 155  0 throw createBodylessComponentException(_activeComponent);
 156   
 157  1070 _activeComponent.addBody(token);
 158    }
 159   
 160  931 private void process(OpenToken token)
 161    {
 162  931 String id = token.getId();
 163  931 IComponent component = null;
 164  931 String componentType = token.getComponentType();
 165   
 166  931 if (componentType == null)
 167  348 component = getEmbeddedComponent(id);
 168    else
 169    {
 170  583 checkForDuplicateId(id, token.getLocation());
 171   
 172  582 component = createImplicitComponent(id, componentType, token.getLocation());
 173    }
 174   
 175    // Make sure the template contains each component only once.
 176   
 177  927 if (_seenIds.contains(id))
 178  0 throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences(
 179    _loadComponent,
 180    id), _loadComponent, token.getLocation(), null);
 181   
 182  927 _seenIds.add(id);
 183   
 184  927 if (_activeComponent == null)
 185  314 _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  613 if (!_activeComponent.getSpecification().getAllowBody())
 192  0 throw createBodylessComponentException(_activeComponent);
 193   
 194  613 _activeComponent.addBody(component);
 195    }
 196   
 197  927 addTemplateBindings(component, token);
 198   
 199  924 _stack[_stackx++] = _activeComponent;
 200   
 201  924 _activeComponent = component;
 202    }
 203   
 204  583 private void checkForDuplicateId(String id, Location location)
 205    {
 206  583 if (id == null)
 207  0 return;
 208   
 209  583 IContainedComponent cc = _loadComponent.getSpecification().getComponent(id);
 210   
 211  583 if (cc != null)
 212  1 throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc),
 213    _loadComponent, location, null);
 214    }
 215   
 216  582 private IComponent createImplicitComponent(String id, String componentType, Location location)
 217    {
 218  582 IComponent result = _pageLoader.createImplicitComponent(
 219    _requestCycle,
 220    _loadComponent,
 221    id,
 222    componentType,
 223    location);
 224   
 225  579 return result;
 226    }
 227   
 228  348 private IComponent getEmbeddedComponent(String id)
 229    {
 230  348 return _loadComponent.getComponent(id);
 231    }
 232   
 233  919 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  919 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  919 _stack[_stackx--] = null;
 245   
 246  919 _activeComponent = _stack[_stackx];
 247    }
 248   
 249  14 private void process(LocalizationToken token)
 250    {
 251  14 IRender render = new LocalizedStringRender(_loadComponent, token);
 252   
 253  14 if (_activeComponent == null)
 254  7 _loadComponent.addOuter(render);
 255    else
 256  7 _activeComponent.addBody(render);
 257    }
 258   
 259    /**
 260    * Adds bindings based on attributes in the template.
 261    */
 262   
 263  927 void addTemplateBindings(IComponent component, OpenToken token)
 264    {
 265  927 IComponentSpecification spec = component.getSpecification();
 266   
 267  927 Map attributes = token.getAttributesMap();
 268   
 269  927 if (attributes != null)
 270    {
 271  510 Iterator i = attributes.entrySet().iterator();
 272   
 273  510 while (i.hasNext())
 274    {
 275  683 Map.Entry entry = (Map.Entry) i.next();
 276   
 277  683 String attributeName = (String) entry.getKey();
 278  683 String value = (String) entry.getValue();
 279   
 280  683 IParameterSpecification pspec = spec.getParameter(attributeName);
 281  683 String parameterName = pspec == null ? attributeName : pspec.getParameterName();
 282   
 283  683 if (!attributeName.equals(parameterName))
 284  0 _log.warn(ImplMessages.usedTemplateParameterAlias(
 285    token,
 286    attributeName,
 287    parameterName));
 288   
 289  683 String description = ImplMessages.templateParameterName(parameterName);
 290   
 291    // Values in a template are always literal, unless prefixed.
 292   
 293  683 IBinding binding = _bindingSource.createBinding(
 294    _loadComponent,
 295    description,
 296    value,
 297    BindingConstants.LITERAL_PREFIX,
 298    token.getLocation());
 299   
 300  683 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  924 if (spec.getParameter(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null
 309    && component.getBinding(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null)
 310    {
 311  6 IBinding binding = _bindingSource.createBinding(
 312    component,
 313    TemplateSource.TEMPLATE_TAG_PARAMETER_NAME,
 314    token.getTag(),
 315    BindingConstants.LITERAL_PREFIX,
 316    token.getLocation());
 317   
 318  6 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  689 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  689 boolean valid = validate(component, spec, name, binding);
 336   
 337  686 if (valid)
 338  661 component.setBinding(name, binding);
 339    }
 340   
 341  689 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  689 boolean isLiteral = binding instanceof LiteralBinding;
 348  689 boolean isBound = component.getBinding(name) != null;
 349  689 boolean isFormal = spec.getParameter(name) != null;
 350   
 351  689 if (!isFormal)
 352    {
 353  56 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  1 if (isLiteral)
 359  0 return false;
 360   
 361  1 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  55 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  25 if (isLiteral)
 375  24 return false;
 376   
 377  1 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  663 if (isBound)
 390    {
 391    // Literal bindings in the template that conflict with bound parameters
 392    // from the spec are silently ignored.
 393   
 394  2 if (isLiteral)
 395  1 return false;
 396   
 397  1 throw new ApplicationRuntimeException(ImplMessages.dupeTemplateBinding(
 398    name,
 399    component,
 400    _loadComponent), component, binding.getLocation(), null);
 401    }
 402   
 403  661 return true;
 404   
 405    }
 406   
 407  199 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  199 Map components = _loadComponent.getComponents();
 413   
 414  199 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  199 if (_seenIds.containsAll(ids))
 420  199 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    }