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