Clover coverage report - Code Coverage for tapestry release 4.0-rc-2
Coverage timestamp: Sat Dec 17 2005 09:39:46 PST
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  196 public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource)
 76    {
 77  196 _log = log;
 78  196 _pageLoader = pageLoader;
 79  196 _bindingSource = bindingSource;
 80    }
 81   
 82  196 public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent,
 83    ComponentTemplate template)
 84    {
 85  196 _requestCycle = requestCycle;
 86  196 _loadComponent = loadComponent;
 87   
 88  196 process(template);
 89    }
 90   
 91  196 private void process(ComponentTemplate template)
 92    {
 93  196 int count = template.getTokenCount();
 94   
 95  196 _stack = new IComponent[count];
 96   
 97  196 for (int i = 0; i < count; i++)
 98    {
 99  3170 TemplateToken token = template.getToken(i);
 100   
 101  3170 TokenType type = token.getType();
 102   
 103  3170 if (type == TokenType.TEXT)
 104    {
 105  1398 process((TextToken) token);
 106  1398 continue;
 107    }
 108   
 109  1772 if (type == TokenType.OPEN)
 110    {
 111  885 process((OpenToken) token);
 112  878 continue;
 113    }
 114   
 115  887 if (type == TokenType.CLOSE)
 116    {
 117  873 process((CloseToken) token);
 118  873 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  189 if (_stackx != 0)
 132  0 throw new ApplicationRuntimeException(Tapestry
 133    .getMessage("BaseComponent.unbalance-open-tags"), _loadComponent, null, null);
 134   
 135  189 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  1398 private void process(TextToken token)
 147    {
 148  1398 if (_activeComponent == null)
 149    {
 150  406 _loadComponent.addOuter(token);
 151  406 return;
 152    }
 153   
 154  992 if (!_activeComponent.getSpecification().getAllowBody())
 155  0 throw createBodylessComponentException(_activeComponent);
 156   
 157  992 _activeComponent.addBody(token);
 158    }
 159   
 160  885 private void process(OpenToken token)
 161    {
 162  885 String id = token.getId();
 163  885 IComponent component = null;
 164  885 String componentType = token.getComponentType();
 165   
 166  885 if (componentType == null)
 167  326 component = getEmbeddedComponent(id);
 168    else
 169    {
 170  559 checkForDuplicateId(id, token.getLocation());
 171   
 172  558 component = createImplicitComponent(id, componentType, token.getLocation());
 173    }
 174   
 175    // Make sure the template contains each component only once.
 176   
 177  881 if (_seenIds.contains(id))
 178  0 throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences(
 179    _loadComponent,
 180    id), _loadComponent, token.getLocation(), null);
 181   
 182  881 _seenIds.add(id);
 183   
 184  881 if (_activeComponent == null)
 185  313 _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  568 if (!_activeComponent.getSpecification().getAllowBody())
 192  0 throw createBodylessComponentException(_activeComponent);
 193   
 194  568 _activeComponent.addBody(component);
 195    }
 196   
 197  881 addTemplateBindings(component, token);
 198   
 199  878 _stack[_stackx++] = _activeComponent;
 200   
 201  878 _activeComponent = component;
 202    }
 203   
 204  559 private void checkForDuplicateId(String id, Location location)
 205    {
 206  559 if (id == null)
 207  0 return;
 208   
 209  559 IContainedComponent cc = _loadComponent.getSpecification().getComponent(id);
 210   
 211  559 if (cc != null)
 212  1 throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc),
 213    _loadComponent, location, null);
 214    }
 215   
 216  558 private IComponent createImplicitComponent(String id, String componentType, Location location)
 217    {
 218  558 IComponent result = _pageLoader.createImplicitComponent(
 219    _requestCycle,
 220    _loadComponent,
 221    id,
 222    componentType,
 223    location);
 224   
 225  555 return result;
 226    }
 227   
 228  326 private IComponent getEmbeddedComponent(String id)
 229    {
 230  326 return _loadComponent.getComponent(id);
 231    }
 232   
 233  873 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  873 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  873 _stack[_stackx--] = null;
 245   
 246  873 _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  881 void addTemplateBindings(IComponent component, OpenToken token)
 264    {
 265  881 IComponentSpecification spec = component.getSpecification();
 266   
 267  881 Map attributes = token.getAttributesMap();
 268   
 269  881 if (attributes != null)
 270    {
 271  491 Iterator i = attributes.entrySet().iterator();
 272   
 273  491 while (i.hasNext())
 274    {
 275  657 Map.Entry entry = (Map.Entry) i.next();
 276   
 277  657 String attributeName = (String) entry.getKey();
 278  657 String value = (String) entry.getValue();
 279   
 280  657 IParameterSpecification pspec = spec.getParameter(attributeName);
 281  657 String parameterName = pspec == null ? attributeName : pspec.getParameterName();
 282   
 283  657 if (!attributeName.equals(parameterName))
 284  0 _log.warn(ImplMessages.usedTemplateParameterAlias(
 285    token,
 286    attributeName,
 287    parameterName));
 288   
 289  657 String description = ImplMessages.templateParameterName(parameterName);
 290   
 291    // Values in a template are always literal, unless prefixed.
 292   
 293  657 IBinding binding = _bindingSource.createBinding(
 294    _loadComponent,
 295    description,
 296    value,
 297    BindingConstants.LITERAL_PREFIX,
 298    token.getLocation());
 299   
 300  657 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  878 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  663 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  663 boolean valid = validate(component, spec, name, binding);
 336   
 337  660 if (valid)
 338  635 component.setBinding(name, binding);
 339    }
 340   
 341  663 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  663 boolean isLiteral = binding instanceof LiteralBinding;
 348  663 boolean isBound = component.getBinding(name) != null;
 349  663 boolean isFormal = spec.getParameter(name) != null;
 350   
 351  663 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  637 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  635 return true;
 404   
 405    }
 406   
 407  189 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  189 Map components = _loadComponent.getComponents();
 413   
 414  189 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  189 if (_seenIds.containsAll(ids))
 420  189 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    }