001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.flatfile.morph;
018
019import java.util.Arrays;
020import java.util.HashSet;
021import java.util.Iterator;
022
023import org.apache.commons.flatfile.Entity;
024import org.apache.commons.flatfile.EntityCollection;
025
026import net.sf.morph.Defaults;
027import net.sf.morph.reflect.IndexedContainerReflector;
028import net.sf.morph.reflect.reflectors.BaseContainerReflector;
029import net.sf.morph.reflect.reflectors.ObjectReflector;
030import net.sf.morph.transform.DecoratedConverter;
031import net.sf.morph.util.ClassUtils;
032
033/**
034 * Base EntityCollection reflector.
035 * @version $Revision: 1301237 $ $Date: 2012-03-15 17:08:23 -0500 (Thu, 15 Mar 2012) $
036 */
037public abstract class BaseEntityCollectionReflector extends BaseContainerReflector {
038    /**
039     * Common property accessor logic.
040     */
041    private abstract class PropertyAccessor {
042        /**
043         * Property name to find
044         */
045        protected String propertyName;
046
047        /**
048         * Create a new PropertyStrategy instance.
049         * @param propertyName to find
050         */
051        protected PropertyAccessor(String propertyName) {
052            this.propertyName = propertyName;
053        }
054
055        /**
056         * Get the property type of <code>bean</code>.
057         * @param bean to inspect
058         * @return Class<?>
059         * @throws Exception on error
060         */
061        final Class<?> getTypeFrom(Object bean) throws Exception {
062            if (isEntity(bean, propertyName)) {
063                return getEntityType(getEntity(bean, propertyName));
064            }
065            return FALLBACK.getType(bean, propertyName);
066        }
067
068        /**
069         * Strategy template method.
070         * @param entity to inspect
071         * @return Class<?>
072         * @throws Exception on error
073         */
074        abstract Class<?> getEntityType(Entity entity) throws Exception;
075
076        /**
077         * Read the property.
078         * @param bean to read
079         * @return value
080         * @throws Exception on error
081         */
082        final Object getFrom(Object bean) throws Exception {
083            if (isEntity(bean, propertyName)) {
084                return getEntityValue(getEntity(bean, propertyName));
085            }
086            return FALLBACK.get(bean, propertyName);
087        }
088
089        /**
090         * Get the value of the specified Entity.
091         * @param entity to read
092         * @return Object value
093         * @throws Exception on error
094         */
095        abstract Object getEntityValue(Entity entity) throws Exception;
096
097        /**
098         * Set the property.
099         * @param bean target
100         * @param propertyValue to set
101         * @throws Exception on error
102         */
103        final void setOn(Object bean, Object propertyValue) throws Exception {
104            if (isEntity(bean, propertyName)) {
105                setEntityValueOn(bean, getEntity(bean, propertyName), propertyValue);
106            } else {
107                FALLBACK.set(bean, propertyName, propertyValue);
108            }
109        }
110
111        /**
112         * Set the entity value.
113         * @param bean target
114         * @param e Entity
115         * @param propertyValue value
116         * @throws Exception on error
117         */
118        abstract void setEntityValueOn(Object bean, Entity e, Object propertyValue)
119                throws Exception;
120
121        /**
122         * Learn whether our property can be read from a given target.
123         * @param bean to inspect
124         * @return boolean
125         * @throws Exception on error
126         */
127        final boolean isReadableOn(Object bean) throws Exception {
128            return isEntity(bean, propertyName) || FALLBACK.isReadable(bean, propertyName);
129        }
130
131        /**
132         * Learn whether our property can be written to a given target.
133         * @param bean to inspect
134         * @return boolean
135         * @throws Exception on error
136         */
137        final boolean isWriteableOn(Object bean) throws Exception {
138            return isEntity(bean, propertyName) || FALLBACK.isWriteable(bean, propertyName);
139        }
140    }
141
142    /**
143     * Overrides the default strategy to treat an Entity as an ordinary javabean.
144     */
145    private class OverridingPropertyAccessor extends PropertyAccessor {
146        /**
147         * Create a new OverridingPropertyAccessor instance.
148         * @param propertyName String
149         */
150        public OverridingPropertyAccessor(String propertyName) {
151            super(propertyName.substring(TYPE_OVERRIDE.length()));
152        }
153
154        /**
155         * {@inheritDoc}
156         */
157        Class<?> getEntityType(Entity e) {
158            return ClassUtils.getClass(e);
159        }
160
161        /**
162         * {@inheritDoc}
163         */
164        Object getEntityValue(Entity e) {
165            return e;
166        }
167
168        /**
169         * {@inheritDoc}
170         */
171        void setEntityValueOn(Object bean, Entity e, Object propertyValue) throws Exception {
172            FALLBACK.set(bean, propertyName, propertyValue);
173        }
174    }
175
176    /**
177     * Default {@link PropertyAccessor}
178     */
179    private class DefaultPropertyAccessor extends PropertyAccessor {
180        /**
181         * Create a new DefaultPropertyAccessor instance.
182         * @param propertyName String
183         */
184        public DefaultPropertyAccessor(String propertyName) {
185            super(propertyName);
186        }
187
188        /**
189         * {@inheritDoc}
190         */
191        Class<?> getEntityType(Entity e) {
192            return e instanceof EntityCollection ? EntityCollection.class : String.class;
193        }
194
195        /**
196         * {@inheritDoc}
197         */
198        Object getEntityValue(Entity e) {
199            return e instanceof EntityCollection ? (Object) e : getString(e);
200        }
201
202        /**
203         * {@inheritDoc}
204         */
205        void setEntityValueOn(Object bean, Entity e, Object propertyValue) throws Exception {
206            setEntityValue(e, propertyValue);
207        }
208    }
209
210    /** Property name prefix indicating to treat Entities as beans. */
211    public static final String TYPE_OVERRIDE = "@";
212
213    private static final ObjectReflector FALLBACK = new ObjectReflector();
214
215    private DecoratedConverter toTextConverter;
216    private boolean trimStrings = true;
217
218    /**
219     * Get the boolean trimStrings.
220     * @return boolean
221     */
222    public boolean isTrimStrings() {
223        return trimStrings;
224    }
225
226    /**
227     * Set the boolean trimStrings.
228     * @param trimStrings boolean
229     */
230    public void setTrimStrings(boolean trimStrings) {
231        this.trimStrings = trimStrings;
232    }
233
234    /**
235     * {@inheritDoc}
236     */
237    @Override
238    protected Class<?> getContainedTypeImpl(@SuppressWarnings("rawtypes") Class c) throws Exception {
239        return Entity.class;
240    }
241
242    /**
243     * {@inheritDoc}
244     */
245    @Override
246    protected Iterator<Entity> getIteratorImpl(Object o) throws Exception {
247        return ((EntityCollection) o).getChildren().iterator();
248    }
249
250    /**
251     * {@inheritDoc}
252     */
253    @Override
254    protected int getSizeImpl(Object o) throws Exception {
255        return ((EntityCollection) o).getChildren().size();
256    }
257
258    /**
259     * {@inheritDoc}
260     */
261    @Override
262    protected Class<?> getTypeImpl(Object bean, String propertyName) throws Exception {
263        return getPropertyAccessor(propertyName).getTypeFrom(bean);
264    }
265
266    /**
267     * {@inheritDoc}
268     */
269    @Override
270    protected Object getImpl(Object container, int index) throws Exception {
271        Entity e = getEntity(container, index);
272        return e instanceof EntityCollection ? (Object) e : getString(e);
273    }
274
275    /**
276     * {@inheritDoc}
277     */
278    @Override
279    protected Object getImpl(Object bean, String propertyName) throws Exception {
280        return getPropertyAccessor(propertyName).getFrom(bean);
281    }
282
283    /**
284     * {@inheritDoc}
285     */
286    @Override
287    protected void setImpl(Object bean, String propertyName, Object value) throws Exception {
288        getPropertyAccessor(propertyName).setOn(bean, value);
289    }
290
291    /**
292     * {@inheritDoc}
293     */
294    @Override
295    protected Object setImpl(Object container, int index, Object propertyValue) throws Exception {
296        Entity e = getEntity(container, index);
297        byte[] result = e.getValue();
298        setEntityValue(e, propertyValue);
299        return result;
300    }
301
302    /**
303     * Set the value of an Entity
304     * @param value Object Set the value of <code>e</code> by converting <code>value</code> to a byte[]
305     * @param e Entity
306     * @throws Exception on error
307     */
308    protected void setEntityValue(Entity e, Object value) throws Exception {
309        e.setValue((byte[]) getToTextConverter().convert(byte[].class, value));
310    }
311
312    /**
313     * {@inheritDoc}
314     */
315    @Override
316    protected String[] getPropertyNamesImpl(Object bean) throws Exception {
317        HashSet<String> result = new HashSet<String>();
318        if (this instanceof IndexedContainerReflector) {
319            result.addAll(Arrays.asList(super.getPropertyNamesImpl(bean)));
320        }
321        String[] fallback = FALLBACK.getPropertyNames(bean);
322        if (fallback != null && fallback.length > 0) {
323            result.addAll(Arrays.asList(fallback));
324            // add override property names:
325            for (int i = 0; i < fallback.length; i++) {
326                result.add("@" + fallback[i]);
327            }
328        }
329        return result.toArray(new String[result.size()]);
330    }
331
332    /**
333     * Learn whether the specified property refers to an Entity child.
334     * @param bean to inspect
335     * @param propertyName to read
336     * @return boolean
337     * @throws Exception on error
338     */
339    protected abstract boolean isEntity(Object bean, String propertyName) throws Exception;
340
341    /**
342     * Get the named child entity.
343     * @param bean to read
344     * @param propertyName to read
345     * @return Entity
346     * @throws Exception on error
347     */
348    protected Entity getEntity(Object bean, String propertyName) throws Exception {
349        return getEntity(bean, Integer.parseInt(propertyName));
350    }
351
352    /**
353     * Get the child entity at index <code>index</code>.
354     * Base implementation throws {@link UnsupportedOperationException}.
355     * @param container to read
356     * @param index to get
357     * @return Entity
358     * @throws Exception on error
359     */
360    protected Entity getEntity(Object container, int index) throws Exception {
361        throw new UnsupportedOperationException();
362    }
363
364    /**
365     * {@inheritDoc}
366     */
367    @Override
368    protected boolean isReadableImpl(Object bean, String propertyName) throws Exception {
369        return getPropertyAccessor(propertyName).isReadableOn(bean);
370    }
371
372    /**
373     * {@inheritDoc}
374     */
375    @Override
376    protected boolean isWriteableImpl(Object bean, String propertyName) throws Exception {
377        return getPropertyAccessor(propertyName).isWriteableOn(bean);
378    }
379
380    /**
381     * Get the to-text {@link DecoratedConverter} assigned.
382     * @return the toTextConverter
383     */
384    public synchronized DecoratedConverter getToTextConverter() {
385        if (toTextConverter == null) {
386            setToTextConverter(Defaults.createToTextConverter());
387        }
388        return toTextConverter;
389    }
390
391    /**
392     * Set the to-text {@link DecoratedConverter}.
393     * @param toTextConverter to set
394     */
395    public synchronized void setToTextConverter(DecoratedConverter toTextConverter) {
396        this.toTextConverter = toTextConverter;
397    }
398
399    /**
400     * Get a PropertyAccessor instance to handle this property.
401     * @param propertyName to handle
402     * @return PropertyStrategy
403     */
404    private PropertyAccessor getPropertyAccessor(String propertyName) {
405        return propertyName.startsWith(TYPE_OVERRIDE) ? (PropertyAccessor) new OverridingPropertyAccessor(
406                propertyName)
407                : new DefaultPropertyAccessor(propertyName);
408    }
409
410    /**
411     * Get the value of the specified Entity as a String, respecting this {@link BaseEntityCollectionReflector}'s
412     * trimStrings property.
413     * @param e Entity
414     * @return e's value as a String.
415     */
416    private String getString(Entity e) {
417        String s = new String(e.getValue());
418        return isTrimStrings() ? s.trim() : s;
419    }
420
421}