001 // Copyright 2007, 2008 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.beaneditor;
016
017 import org.apache.tapestry5.PropertyConduit;
018 import org.apache.tapestry5.beaneditor.BeanModel;
019 import org.apache.tapestry5.beaneditor.PropertyModel;
020 import org.apache.tapestry5.beaneditor.RelativePosition;
021 import org.apache.tapestry5.internal.services.CoercingPropertyConduitWrapper;
022 import org.apache.tapestry5.ioc.Messages;
023 import org.apache.tapestry5.ioc.ObjectLocator;
024 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
025 import org.apache.tapestry5.ioc.internal.util.Defense;
026 import org.apache.tapestry5.ioc.services.ClassFabUtils;
027 import org.apache.tapestry5.ioc.services.TypeCoercer;
028 import org.apache.tapestry5.services.PropertyConduitSource;
029
030 import java.util.List;
031 import java.util.Map;
032
033 public class BeanModelImpl<T> implements BeanModel<T>
034 {
035 private final Class<T> beanType;
036
037 private final PropertyConduitSource propertyConduitSource;
038
039 private final TypeCoercer typeCoercer;
040
041 private final Messages messages;
042
043 private final ObjectLocator locator;
044
045 private final Map<String, PropertyModel> properties = CollectionFactory.newCaseInsensitiveMap();
046
047 // The list of property names, in desired order (generally not alphabetical order).
048
049 private final List<String> propertyNames = CollectionFactory.newList();
050
051 public BeanModelImpl(
052 Class<T> beanType, PropertyConduitSource
053 propertyConduitSource,
054 TypeCoercer typeCoercer, Messages
055 messages, ObjectLocator locator)
056
057 {
058 this.beanType = beanType;
059 this.propertyConduitSource = propertyConduitSource;
060 this.typeCoercer = typeCoercer;
061 this.messages = messages;
062 this.locator = locator;
063 }
064
065 public Class<T> getBeanType()
066 {
067 return beanType;
068 }
069
070 public T newInstance()
071 {
072 return locator.autobuild(beanType);
073 }
074
075 public PropertyModel add(String propertyName)
076 {
077 PropertyConduit conduit = createConduit(propertyName);
078
079 return add(propertyName, conduit);
080 }
081
082 private void validateNewPropertyName(String propertyName)
083 {
084 Defense.notBlank(propertyName, "propertyName");
085
086 if (properties.containsKey(propertyName))
087 throw new RuntimeException(BeanEditorMessages.duplicatePropertyName(
088 beanType,
089 propertyName));
090 }
091
092 public PropertyModel add(RelativePosition position, String existingPropertyName,
093 String propertyName, PropertyConduit conduit)
094 {
095 Defense.notNull(position, "position");
096
097 validateNewPropertyName(propertyName);
098
099 // Locate the existing one.
100
101 PropertyModel existing = get(existingPropertyName);
102
103 // Use the case normalized property name.
104
105 int pos = propertyNames.indexOf(existing.getPropertyName());
106
107 PropertyModel newModel = new PropertyModelImpl(this, propertyName, conduit, messages);
108
109 properties.put(propertyName, newModel);
110
111 int offset = position == RelativePosition.AFTER ? 1 : 0;
112
113 propertyNames.add(pos + offset, propertyName);
114
115 return newModel;
116 }
117
118 public PropertyModel add(RelativePosition position, String existingPropertyName,
119 String propertyName)
120 {
121 PropertyConduit conduit = createConduit(propertyName);
122
123 return add(position, existingPropertyName, propertyName, conduit);
124 }
125
126 public PropertyModel add(String propertyName, PropertyConduit conduit)
127 {
128 validateNewPropertyName(propertyName);
129
130 PropertyModel propertyModel = new PropertyModelImpl(this, propertyName, conduit, messages);
131
132 properties.put(propertyName, propertyModel);
133
134 // Remember the order in which the properties were added.
135
136 propertyNames.add(propertyName);
137
138 return propertyModel;
139 }
140
141 private CoercingPropertyConduitWrapper createConduit(String propertyName)
142 {
143 return new CoercingPropertyConduitWrapper(propertyConduitSource.create(beanType,
144 propertyName), typeCoercer);
145 }
146
147 public PropertyModel get(String propertyName)
148 {
149 PropertyModel propertyModel = properties.get(propertyName);
150
151 if (propertyModel == null)
152 throw new RuntimeException(BeanEditorMessages.unknownProperty(beanType,
153 propertyName,
154 properties.keySet()));
155
156 return propertyModel;
157 }
158
159 public PropertyModel getById(String propertyId)
160 {
161 for (PropertyModel model : properties.values())
162 {
163 if (model.getId().equalsIgnoreCase(propertyId)) return model;
164 }
165
166 // Not found, so we throw an exception. A bit of work to set
167 // up the exception however.
168
169 List<String> ids = CollectionFactory.newList();
170
171 for (PropertyModel model : properties.values())
172 {
173 ids.add(model.getId());
174 }
175
176 throw new RuntimeException(BeanEditorMessages.unknownPropertyId(beanType,
177 propertyId, ids));
178
179 }
180
181 public List<String> getPropertyNames()
182 {
183 return CollectionFactory.newList(propertyNames);
184 }
185
186 public BeanModel exclude(String... propertyNames)
187 {
188 for (String propertyName : propertyNames)
189 {
190 PropertyModel model = properties.get(propertyName);
191
192 if (model == null) continue;
193
194 // De-referencing from the model is needed because the name provided may not be a
195 // case-exact match, so we get the normalized or canonical name from the model because
196 // that's the one in propertyNames.
197
198 this.propertyNames.remove(model.getPropertyName());
199
200 properties.remove(propertyName);
201 }
202
203 return this;
204 }
205
206 public BeanModel reorder(String... propertyNames)
207 {
208 List<String> remainingPropertyNames = CollectionFactory.newList(this.propertyNames);
209 List<String> reorderedPropertyNames = CollectionFactory.newList();
210
211 for (String name : propertyNames)
212 {
213 PropertyModel model = get(name);
214
215 // Get the canonical form (which may differ from name in terms of case)
216 String canonical = model.getPropertyName();
217
218 reorderedPropertyNames.add(canonical);
219
220 remainingPropertyNames.remove(canonical);
221 }
222
223 this.propertyNames.clear();
224 this.propertyNames.addAll(reorderedPropertyNames);
225
226 // Any unspecified names are ordered to the end. Don't want them? Remove them instead.
227 this.propertyNames.addAll(remainingPropertyNames);
228
229 return this;
230 }
231
232 public BeanModel include(String... propertyNames)
233 {
234 List<String> reorderedPropertyNames = CollectionFactory.newList();
235 Map<String, PropertyModel> reduced = CollectionFactory.newCaseInsensitiveMap();
236
237
238 for (String name : propertyNames)
239 {
240
241 PropertyModel model = get(name);
242
243 String canonical = model.getPropertyName();
244
245 reorderedPropertyNames.add(canonical);
246 reduced.put(canonical, model);
247
248 }
249
250 this.propertyNames.clear();
251 this.propertyNames.addAll(reorderedPropertyNames);
252
253 properties.clear();
254 properties.putAll(reduced);
255
256 return this;
257 }
258
259 @Override
260 public String toString()
261 {
262 StringBuilder builder = new StringBuilder("BeanModel[");
263 builder.append(ClassFabUtils.toJavaClassName(beanType));
264
265 builder.append(" properties:");
266 String sep = "";
267
268 for (String name : propertyNames)
269 {
270 builder.append(sep);
271 builder.append(name);
272
273 sep = ", ";
274 }
275
276 builder.append("]");
277
278 return builder.toString();
279 }
280 }