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    }