001// Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 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.model; 016 017import org.apache.tapestry5.commons.Location; 018import org.apache.tapestry5.commons.Resource; 019import org.apache.tapestry5.commons.util.CollectionFactory; 020import org.apache.tapestry5.ioc.internal.util.InternalUtils; 021import org.apache.tapestry5.ioc.util.IdAllocator; 022import org.apache.tapestry5.model.*; 023import org.slf4j.Logger; 024 025import java.util.Collections; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030/** 031 * Internal implementation of {@link org.apache.tapestry5.model.MutableComponentModel}. 032 */ 033public final class MutableComponentModelImpl implements MutableComponentModel 034{ 035 private final ComponentModel parentModel; 036 037 private final Resource baseResource; 038 039 private final String componentClassName; 040 041 private final IdAllocator persistentFieldNameAllocator = new IdAllocator(); 042 043 private final Logger logger; 044 045 private final boolean pageClass; 046 047 private Map<String, ParameterModel> parameters; 048 049 private Map<String, EmbeddedComponentModel> embeddedComponents; 050 051 /** 052 * Maps from field name to strategy. 053 */ 054 private Map<String, String> persistentFields; 055 056 private List<String> mixinClassNames; 057 058 private Map<String, String[]> mixinOrders; 059 060 private boolean informalParametersSupported; 061 062 private boolean mixinAfter; 063 064 private Map<String, String> metaData; 065 066 private Set<Class> handledRenderPhases; 067 068 private Map<String, Boolean> handledEvents; 069 070 private final String libraryName; 071 072 private boolean handleActivationEventContext; 073 074 public MutableComponentModelImpl(String componentClassName, Logger logger, Resource baseResource, 075 ComponentModel parentModel, boolean pageClass, String libraryName) 076 { 077 this.componentClassName = componentClassName; 078 this.logger = logger; 079 this.baseResource = baseResource; 080 this.parentModel = parentModel; 081 this.pageClass = pageClass; 082 this.libraryName = libraryName; 083 084 // Default to false, explicitly set during page transformation 085 this.handleActivationEventContext = false; 086 087 // Pre-allocate names from the parent, to avoid name collisions. 088 089 if (this.parentModel != null) 090 { 091 for (String name : this.parentModel.getPersistentFieldNames()) 092 { 093 persistentFieldNameAllocator.allocateId(name); 094 } 095 } 096 } 097 098 public String getLibraryName() 099 { 100 return libraryName; 101 } 102 103 @Override 104 public String toString() 105 { 106 return String.format("ComponentModel[%s]", componentClassName); 107 } 108 109 public Logger getLogger() 110 { 111 return logger; 112 } 113 114 public Resource getBaseResource() 115 { 116 return baseResource; 117 } 118 119 public String getComponentClassName() 120 { 121 return componentClassName; 122 } 123 124 public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix, 125 boolean cached) 126 { 127 assert InternalUtils.isNonBlank(name); 128 assert InternalUtils.isNonBlank(defaultBindingPrefix); 129 130 if (parameters == null) 131 { 132 parameters = CollectionFactory.newCaseInsensitiveMap(); 133 } 134 135 if (parameters.containsKey(name)) 136 { 137 throw new IllegalArgumentException(String.format("Parameter '%s' of component class %s is already defined.", name, componentClassName)); 138 } 139 140 ParameterModel existingModel = getParameterModel(name); 141 142 if (existingModel != null) 143 { 144 throw new IllegalArgumentException(String.format("Parameter '%s' of component class %s conflicts with the parameter defined by the %s base class.", 145 name, componentClassName, existingModel.getComponentModel().getComponentClassName())); 146 } 147 148 parameters.put(name, new ParameterModelImpl(this, name, required, allowNull, defaultBindingPrefix, cached)); 149 } 150 151 public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix) 152 { 153 // assume /false/ for the default because: 154 // if the parameter is actually cached, the only effect will be to reduce that optimization 155 // in certain 156 // scenarios (mixin BindParameter). But if the value is NOT cached but we say it is, 157 // we'll get incorrect behavior. 158 addParameter(name, required, allowNull, defaultBindingPrefix, false); 159 } 160 161 public ParameterModel getParameterModel(String parameterName) 162 { 163 ParameterModel result = InternalUtils.get(parameters, parameterName); 164 165 if (result == null && parentModel != null) 166 result = parentModel.getParameterModel(parameterName); 167 168 return result; 169 } 170 171 public boolean isFormalParameter(String parameterName) 172 { 173 return getParameterModel(parameterName) != null; 174 } 175 176 public List<String> getParameterNames() 177 { 178 List<String> names = CollectionFactory.newList(); 179 180 if (parameters != null) 181 names.addAll(parameters.keySet()); 182 183 if (parentModel != null) 184 names.addAll(parentModel.getParameterNames()); 185 186 Collections.sort(names); 187 188 return names; 189 } 190 191 public List<String> getDeclaredParameterNames() 192 { 193 return InternalUtils.sortedKeys(parameters); 194 } 195 196 public MutableEmbeddedComponentModel addEmbeddedComponent(String id, String type, String componentClassName, 197 boolean inheritInformalParameters, Location location) 198 { 199 // TODO: Parent compent model? Or would we simply override the parent? 200 201 if (embeddedComponents == null) 202 embeddedComponents = CollectionFactory.newCaseInsensitiveMap(); 203 else if (embeddedComponents.containsKey(id)) 204 throw new IllegalArgumentException(String.format("Embedded component '%s' has already been defined for component class %s.", id, this.componentClassName)); 205 206 MutableEmbeddedComponentModel embedded = new MutableEmbeddedComponentModelImpl(id, type, componentClassName, 207 this.componentClassName, inheritInformalParameters, location); 208 209 embeddedComponents.put(id, embedded); 210 211 return embedded; // So that parameters can be filled in 212 } 213 214 public List<String> getEmbeddedComponentIds() 215 { 216 List<String> result = CollectionFactory.newList(); 217 218 if (embeddedComponents != null) 219 result.addAll(embeddedComponents.keySet()); 220 221 if (parentModel != null) 222 result.addAll(parentModel.getEmbeddedComponentIds()); 223 224 Collections.sort(result); 225 226 return result; 227 } 228 229 public EmbeddedComponentModel getEmbeddedComponentModel(String componentId) 230 { 231 EmbeddedComponentModel result = InternalUtils.get(embeddedComponents, componentId); 232 233 if (result == null && parentModel != null) 234 result = parentModel.getEmbeddedComponentModel(componentId); 235 236 return result; 237 } 238 239 public String getFieldPersistenceStrategy(String fieldName) 240 { 241 String result = InternalUtils.get(persistentFields, fieldName); 242 243 if (result == null && parentModel != null) 244 result = parentModel.getFieldPersistenceStrategy(fieldName); 245 246 if (result == null) 247 throw new IllegalArgumentException(String.format("No field persistence strategy has been defined for field '%s'.", fieldName)); 248 249 return result; 250 } 251 252 public List<String> getPersistentFieldNames() 253 { 254 return persistentFieldNameAllocator.getAllocatedIds(); 255 } 256 257 public String setFieldPersistenceStrategy(String fieldName, String strategy) 258 { 259 String logicalFieldName = persistentFieldNameAllocator.allocateId(fieldName); 260 261 if (persistentFields == null) 262 persistentFields = CollectionFactory.newMap(); 263 264 persistentFields.put(logicalFieldName, strategy); 265 266 return logicalFieldName; 267 } 268 269 public boolean isRootClass() 270 { 271 return parentModel == null; 272 } 273 274 public void addMixinClassName(String mixinClassName, String... order) 275 { 276 if (mixinClassNames == null) 277 mixinClassNames = CollectionFactory.newList(); 278 279 mixinClassNames.add(mixinClassName); 280 if (order != null && order.length > 0) 281 { 282 if (mixinOrders == null) 283 mixinOrders = CollectionFactory.newCaseInsensitiveMap(); 284 mixinOrders.put(mixinClassName, order); 285 } 286 } 287 288 public List<String> getMixinClassNames() 289 { 290 List<String> result = CollectionFactory.newList(); 291 292 if (mixinClassNames != null) 293 result.addAll(mixinClassNames); 294 295 if (parentModel != null) 296 result.addAll(parentModel.getMixinClassNames()); 297 298 Collections.sort(result); 299 300 return result; 301 } 302 303 public void enableSupportsInformalParameters() 304 { 305 informalParametersSupported = true; 306 } 307 308 public boolean getSupportsInformalParameters() 309 { 310 return informalParametersSupported; 311 } 312 313 public ComponentModel getParentModel() 314 { 315 return parentModel; 316 } 317 318 public boolean isMixinAfter() 319 { 320 return mixinAfter; 321 } 322 323 public void setMixinAfter(boolean mixinAfter) 324 { 325 this.mixinAfter = mixinAfter; 326 } 327 328 public void setMeta(String key, String value) 329 { 330 assert InternalUtils.isNonBlank(key); 331 assert InternalUtils.isNonBlank(value); 332 if (metaData == null) 333 metaData = CollectionFactory.newCaseInsensitiveMap(); 334 335 // TODO: Error if duplicate? 336 337 metaData.put(key, value); 338 } 339 340 public void addRenderPhase(Class renderPhase) 341 { 342 assert renderPhase != null; 343 if (handledRenderPhases == null) 344 handledRenderPhases = CollectionFactory.newSet(); 345 346 handledRenderPhases.add(renderPhase); 347 } 348 349 public void addEventHandler(String eventType) 350 { 351 if (handledEvents == null) 352 handledEvents = CollectionFactory.newCaseInsensitiveMap(); 353 354 handledEvents.put(eventType, true); 355 } 356 357 public String getMeta(String key) 358 { 359 String result = InternalUtils.get(metaData, key); 360 361 if (result == null && parentModel != null) 362 result = parentModel.getMeta(key); 363 364 return result; 365 } 366 367 public Set<Class> getHandledRenderPhases() 368 { 369 Set<Class> result = CollectionFactory.newSet(); 370 371 if (parentModel != null) 372 result.addAll(parentModel.getHandledRenderPhases()); 373 374 if (handledRenderPhases != null) 375 result.addAll(handledRenderPhases); 376 377 return result; 378 } 379 380 public boolean handlesEvent(String eventType) 381 { 382 if (InternalUtils.get(handledEvents, eventType) != null) 383 return true; 384 385 return parentModel == null ? false : parentModel.handlesEvent(eventType); 386 } 387 388 public String[] getOrderForMixin(String mixinClassName) 389 { 390 final String[] orders = InternalUtils.get(mixinOrders, mixinClassName); 391 392 if (orders == null && parentModel != null) 393 return parentModel.getOrderForMixin(mixinClassName); 394 395 return orders; 396 } 397 398 public boolean isPage() 399 { 400 return pageClass; 401 } 402 403 public void doHandleActivationEventContext() 404 { 405 this.handleActivationEventContext = true; 406 } 407 408 public boolean handleActivationEventContext() 409 { 410 return this.handleActivationEventContext; 411 } 412}