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     */
017    package org.apache.camel.dataformat.bindy;
018    
019    import java.lang.reflect.Field;
020    import java.math.BigDecimal;
021    import java.math.BigInteger;
022    import java.text.NumberFormat;
023    import java.util.ArrayList;
024    import java.util.Date;
025    import java.util.HashMap;
026    import java.util.LinkedHashMap;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    
031    import org.apache.camel.dataformat.bindy.format.BigDecimalFormat;
032    import org.apache.camel.dataformat.bindy.format.BigIntegerFormat;
033    import org.apache.camel.dataformat.bindy.format.ByteFormat;
034    import org.apache.camel.dataformat.bindy.format.BytePatternFormat;
035    import org.apache.camel.dataformat.bindy.format.CharacterFormat;
036    import org.apache.camel.dataformat.bindy.format.DatePatternFormat;
037    import org.apache.camel.dataformat.bindy.format.DoubleFormat;
038    import org.apache.camel.dataformat.bindy.format.DoublePatternFormat;
039    import org.apache.camel.dataformat.bindy.format.FloatFormat;
040    import org.apache.camel.dataformat.bindy.format.FloatPatternFormat;
041    import org.apache.camel.dataformat.bindy.format.IntegerFormat;
042    import org.apache.camel.dataformat.bindy.format.IntegerPatternFormat;
043    import org.apache.camel.dataformat.bindy.format.LongFormat;
044    import org.apache.camel.dataformat.bindy.format.LongPatternFormat;
045    import org.apache.camel.dataformat.bindy.format.ShortFormat;
046    import org.apache.camel.dataformat.bindy.format.ShortPatternFormat;
047    import org.apache.camel.dataformat.bindy.format.StringFormat;
048    import org.apache.camel.dataformat.bindy.util.AnnotationModelLoader;
049    import org.apache.camel.spi.PackageScanClassResolver;
050    import org.apache.camel.util.ObjectHelper;
051    import org.apache.commons.logging.Log;
052    import org.apache.commons.logging.LogFactory;
053    
054    /**
055     * The BindyAbstractFactory implements what its common to all the formats
056     * supported by camel bindy
057     */
058    public abstract class BindyAbstractFactory implements BindyFactory {
059        private static final transient Log LOG = LogFactory.getLog(BindyAbstractFactory.class);
060        protected Set<Class> models;
061        protected Map<String, List<Field>> annotedLinkFields = new LinkedHashMap<String, List<Field>>();
062        protected List<Field> linkFields = new ArrayList<Field>();
063        protected String crlf;
064    
065        private AnnotationModelLoader modelsLoader;
066        private String[] packageNames;
067    
068        public BindyAbstractFactory(PackageScanClassResolver resolver, String... packageNames) throws Exception {
069            this.modelsLoader = new AnnotationModelLoader(resolver);
070            this.packageNames = packageNames;
071    
072            if (LOG.isDebugEnabled()) {
073                LOG.debug("Package(s) name : " + packageNames.toString());
074            }
075    
076            initModel();
077        }
078    
079        /**
080         * method uses to initialize the model representing the classes who will
081         * bind the data. This process will scan for classes according to the
082         * package name provided, check the annotated classes and fields.
083         * 
084         * @throws Exception
085         */
086        public void initModel() throws Exception {
087            // Find classes defined as Model
088            initModelClasses(this.packageNames);
089        }
090    
091        /**
092         * Find all the classes defined as model
093         */
094        private void initModelClasses(String... packageNames) throws Exception {
095            models = modelsLoader.loadModels(packageNames);
096        }
097    
098        /**
099         * Find fields annoted in each class of the model
100         */
101        public abstract void initAnnotedFields() throws Exception;
102    
103        public abstract void bind(List<String> data, Map<String, Object> model) throws Exception;
104    
105        public abstract String unbind(Map<String, Object> model) throws Exception;
106    
107        /**
108         * Link objects together
109         */
110        public void link(Map<String, Object> model) throws Exception {
111    
112            // Iterate class by class
113            for (String link : annotedLinkFields.keySet()) {
114                List<Field> linkFields = annotedLinkFields.get(link);
115    
116                // Iterate through Link fields list
117                for (Field field : linkFields) {
118    
119                    // Change protection for private field
120                    field.setAccessible(true);
121    
122                    // Retrieve linked object
123                    String toClassName = field.getType().getName();
124                    Object to = model.get(toClassName);
125    
126                    ObjectHelper.notNull(to, "No @link annotation has been defined for the oject to link");
127                    field.set(model.get(field.getDeclaringClass().getName()), to);
128    
129                }
130            }
131        }
132    
133        /**
134         * Factory method generating new instances of the model and adding them to a
135         * HashMap
136         * 
137         * @return Map is a collection of the objects used to bind data from
138         *         records, messages
139         * @throws Exception
140         *         can be thrown
141         */
142        public Map<String, Object> factory() throws Exception {
143            Map<String, Object> mapModel = new HashMap<String, Object>();
144    
145            for (Class<?> cl : models) {
146                Object obj = ObjectHelper.newInstance(cl);
147    
148                // Add instance of the class to the Map Model
149                mapModel.put(obj.getClass().getName(), obj);
150            }
151    
152            return mapModel;
153        }
154    
155        /**
156         * Generate a unique key
157         * 
158         * @param key1
159         *            The key of the section number
160         * @param key2
161         *            The key of the position of the field
162         * @return the key generated
163         */
164        protected static Integer generateKey(Integer key1, Integer key2) {
165            String key2Formated = getNumberFormat().format((long)key2);
166            String keyGenerated = String.valueOf(key1) + key2Formated;
167            
168            return Integer.valueOf(keyGenerated);
169        }
170    
171        /**
172         * 
173         * @return NumberFormat
174         */
175        private static NumberFormat getNumberFormat() {
176            // Get instance of NumberFormat
177            NumberFormat nf = NumberFormat.getInstance();
178        
179            // set max number of digits to 3 (thousands) 
180            nf.setMaximumIntegerDigits(3);
181            nf.setMinimumIntegerDigits(3);
182            
183            return nf;
184        }
185        
186        public static Object getDefaultValueforPrimitive(Class<?> clazz) throws Exception {
187            if (clazz == byte.class || clazz == Byte.class) {
188                return Byte.MIN_VALUE;
189            } else if (clazz == short.class || clazz == Short.class) {
190                return Short.MIN_VALUE;
191            } else if (clazz == int.class || clazz == Integer.class) {
192                return Integer.MIN_VALUE;
193            } else if (clazz == long.class || clazz == Long.class) {
194                return Long.MIN_VALUE;
195            } else if (clazz == float.class || clazz == Float.class) {
196                return Float.MIN_VALUE;
197            } else if (clazz == double.class || clazz == Double.class) {
198                return Double.MIN_VALUE;
199            } else if (clazz == BigDecimal.class) {
200                return BigDecimal.ZERO;
201            } else if (clazz == BigInteger.class) {
202                return BigInteger.ZERO;
203            } else if (clazz == String.class) {
204                return null;
205            } else if (clazz == Date.class) {
206                return null;
207            } else if (clazz == char.class || clazz == Character.class) {
208                return null;
209            } else if (clazz == boolean.class || clazz == Boolean.class) {
210                return false;
211            } else {
212                throw new IllegalArgumentException("Can not find type corresponding : " + clazz.getCanonicalName());
213            }
214        }
215    
216        /**
217         * Find the carriage return set
218         */
219        public String getCarriageReturn() {
220            return crlf;
221        }
222    }