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 }