View Javadoc

1   /*
2    * Copyright 2004 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.ancientprogramming.fixedformat4j.format.impl;
17  
18  import com.ancientprogramming.fixedformat4j.annotation.*;
19  import com.ancientprogramming.fixedformat4j.exception.FixedFormatException;
20  import com.ancientprogramming.fixedformat4j.format.*;
21  import static com.ancientprogramming.fixedformat4j.format.FixedFormatUtil.fetchData;
22  import static com.ancientprogramming.fixedformat4j.format.FixedFormatUtil.getFixedFormatterInstance;
23  import com.ancientprogramming.fixedformat4j.format.data.FixedFormatBooleanData;
24  import com.ancientprogramming.fixedformat4j.format.data.FixedFormatDecimalData;
25  import com.ancientprogramming.fixedformat4j.format.data.FixedFormatNumberData;
26  import com.ancientprogramming.fixedformat4j.format.data.FixedFormatPatternData;
27  import org.apache.commons.lang.StringUtils;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import static java.lang.String.format;
32  import java.lang.reflect.Constructor;
33  import java.lang.reflect.Method;
34  import java.util.HashMap;
35  import java.util.Set;
36  
37  /**
38   * Load and export data to and from fixedformat
39   *
40   * @author Jacob von Eyben - http://www.ancientprogramming.com
41   * @since 1.0.0
42   */
43  public class FixedFormatManagerImpl implements FixedFormatManager {
44  
45    private static final Log LOG = LogFactory.getLog(FixedFormatManagerImpl.class);
46  
47    public <T> T load(Class<T> fixedFormatRecordClass, String data) {
48      HashMap<String, Object> foundData = new HashMap<String, Object>();
49      HashMap<String, Class<?>> methodClass = new HashMap<String, Class<?>>();
50      //assert the record is marked with a Record
51      getAndAssertRecordAnnotation(fixedFormatRecordClass);
52  
53      //create instance to set data into
54      T instance;
55      try {
56        Constructor<T> constructor = fixedFormatRecordClass.getDeclaredConstructor();
57        instance = constructor.newInstance();
58      } catch (NoSuchMethodException e) {
59        //If the class is a possible inner class do some more work
60        Class declaringClass = fixedFormatRecordClass.getDeclaringClass();
61        if (declaringClass != null) {
62          try {
63            Object declaringClassInstance = null;
64            try {
65              Constructor declaringClassConstructor = declaringClass.getDeclaredConstructor();
66              declaringClassInstance = declaringClassConstructor.newInstance();
67            } catch (NoSuchMethodException dex) {
68              throw new FixedFormatException(format("Trying to create instance of innerclass %s, but the declaring class %s is missing a default constructor which is nessesary to be loaded through %s", fixedFormatRecordClass.getName(), declaringClass.getName(), getClass().getName()));
69            } catch (Exception de) {
70              throw new FixedFormatException(format("unable to create instance of declaring class %s, which is needed to instansiate %s", declaringClass.getName(), fixedFormatRecordClass.getName()), e);
71            }
72            Constructor<T> constructor = fixedFormatRecordClass.getDeclaredConstructor(declaringClass);
73            instance = constructor.newInstance(declaringClassInstance);
74          } catch (FixedFormatException ex) {
75            throw ex;
76          } catch (NoSuchMethodException ex) {
77            throw new FixedFormatException(format("%s is missing a default constructor which is nessesary to be loaded through %s", fixedFormatRecordClass.getName(), getClass().getName()));
78          } catch (Exception ex) {
79            throw new FixedFormatException(format("unable to create instance of %s", fixedFormatRecordClass.getName()), e);
80          }
81        } else {
82          throw new FixedFormatException(format("%s is missing a default constructor which is nessesary to be loaded through %s", fixedFormatRecordClass.getName(), getClass().getName()));
83        }
84      } catch (Exception e) {
85        throw new FixedFormatException(format("unable to create instance of %s", fixedFormatRecordClass.getName()), e);
86      }
87  
88      //look for setter annotations and read data from the 'data' string
89      Method[] allMethods = fixedFormatRecordClass.getMethods();
90      for (Method method : allMethods) {
91        String methodName = stripMethodPrefix(method.getName());
92        Field fieldAnnotation = method.getAnnotation(Field.class);
93        Fields fieldsAnnotation = method.getAnnotation(Fields.class);
94        if (fieldAnnotation != null) {
95          Object loadedData = readDataAccordingFieldAnnotation(fixedFormatRecordClass, data, method, fieldAnnotation);
96          foundData.put(methodName, loadedData);
97          methodClass.put(methodName, method.getReturnType());
98        } else if (fieldsAnnotation != null) {
99          //assert that the fields annotation contains minimum one field anno
100         if (fieldsAnnotation.value() == null || fieldsAnnotation.value().length == 0) {
101           throw new FixedFormatException(format("%s annotation must contain minimum one %s annotation", Fields.class.getName(), Field.class.getName()));
102         }
103         Object loadedData = readDataAccordingFieldAnnotation(fixedFormatRecordClass, data, method, fieldsAnnotation.value()[0]);
104         foundData.put(methodName, loadedData);
105         methodClass.put(methodName, method.getReturnType());
106       }
107     }
108 
109     Set<String> keys = foundData.keySet();
110     for (String key : keys) {
111       String setterMethodName = "set" + key;
112 
113       Object foundDataObj = foundData.get(key);
114       if (foundDataObj != null) {
115     	Class datatype = methodClass.get(key);
116         Method method;
117         try {
118           method = fixedFormatRecordClass.getMethod(setterMethodName, datatype);
119         } catch (NoSuchMethodException e) {
120           throw new FixedFormatException(format("setter method named %s.%s(%s) does not exist", fixedFormatRecordClass.getName(), setterMethodName, datatype));
121         }
122         try {
123           method.invoke(instance, foundData.get(key));
124         } catch (Exception e) {
125           throw new FixedFormatException(format("could not invoke method %s.%s(%s)", fixedFormatRecordClass.getName(), setterMethodName, datatype), e);
126         }
127       }
128 
129     }
130     return instance;
131   }
132 
133   public <T> String export(String existingData, T fixedFormatRecord) {
134     StringBuffer result = new StringBuffer(existingData);
135     Record record = getAndAssertRecordAnnotation(fixedFormatRecord.getClass());
136 
137     Class fixedFormatRecordClass = fixedFormatRecord.getClass();
138     HashMap<Integer, String> foundData = new HashMap<Integer, String>(); // hashmap containing offset and data to write
139     Method[] allMethods = fixedFormatRecordClass.getMethods();
140     for (Method method : allMethods) {
141       Field fieldAnnotation = method.getAnnotation(Field.class);
142       Fields fieldsAnnotation = method.getAnnotation(Fields.class);
143       if (fieldAnnotation != null) {
144         String exportedData = exportDataAccordingFieldAnnotation(fixedFormatRecord, method, fieldAnnotation);
145         foundData.put(fieldAnnotation.offset(), exportedData);
146       } else if (fieldsAnnotation != null) {
147         Field[] fields = fieldsAnnotation.value();
148         for (Field field : fields) {
149           String exportedData = exportDataAccordingFieldAnnotation(fixedFormatRecord, method, field);
150           foundData.put(field.offset(), exportedData);
151         }
152       }
153     }
154 
155     Set<Integer> sortedoffsets = foundData.keySet();
156     for (Integer offset : sortedoffsets) {
157       String data = foundData.get(offset);
158       appendData(result, record.paddingChar(), offset, data);
159     }
160 
161     if (record.length() != -1) { //pad with paddingchar
162       while (result.length() < record.length()) {
163         result.append(record.paddingChar());
164       }
165     }
166     return result.toString();
167   }
168 
169   public <T> String export(T fixedFormatRecord) {
170     return export("", fixedFormatRecord);
171   }
172 
173   private void appendData(StringBuffer result, Character paddingChar, Integer offset, String data) {
174     int zeroBasedOffset = offset - 1;
175     while (result.length() < zeroBasedOffset) {
176       result.append(paddingChar);
177     }
178     int length = data.length();
179     if (result.length() < zeroBasedOffset + length) {
180       result.append(StringUtils.leftPad("", (zeroBasedOffset + length) - result.length(), paddingChar));
181     }
182     result.replace(zeroBasedOffset, zeroBasedOffset + length, data);
183   }
184 
185 
186   private <T> Record getAndAssertRecordAnnotation(Class<T> fixedFormatRecordClass) {
187     Record recordAnno = fixedFormatRecordClass.getAnnotation(Record.class);
188     if (recordAnno == null) {
189       throw new FixedFormatException(format("%s has to be marked with the record annotation to be loaded", fixedFormatRecordClass.getName()));
190     }
191     return recordAnno;
192   }
193 
194   private <T> Object readDataAccordingFieldAnnotation(Class<T> clazz, String data, Method method, Field fieldAnno) throws ParseException {
195     Class datatype = getDatatype(method, fieldAnno);
196 
197     FormatContext context = getFormatContext(datatype, fieldAnno);
198     FixedFormatter formatter = getFixedFormatterInstance(context.getFormatter(), context);
199     FormatInstructions formatdata = getFormatInstructions(method, fieldAnno);
200 
201     String dataToParse = fetchData(data, formatdata, context);
202     Object loadedData;
203     try {
204     loadedData = formatter.parse(dataToParse, formatdata);
205     } catch (RuntimeException e) {
206       throw new ParseException(data, dataToParse, clazz, method, context, formatdata, e);
207     }
208     if (LOG.isDebugEnabled()) {
209       LOG.debug("the loaded data[" + loadedData + "]");
210     }
211     return loadedData;
212   }
213 
214   private Class getDatatype(Method method, Field fieldAnno) {
215     Class datatype;
216     if (followsBeanStandard(method)) {
217       datatype = method.getReturnType();
218     } else {
219       throw new FixedFormatException(format("Cannot annotate method %s, with %s annotation. %s annotations must be placed on methods starting with 'get' or 'is'", method.getName(), fieldAnno.getClass().getName(), fieldAnno.getClass().getName()));
220     }
221     return datatype;
222   }
223 
224   private <T> String exportDataAccordingFieldAnnotation(T fixedFormatRecord, Method method, Field fieldAnno) {
225     String result;
226     Class datatype = getDatatype(method, fieldAnno);
227 
228     FormatContext context = getFormatContext(datatype, fieldAnno);
229     FixedFormatter formatter = getFixedFormatterInstance(context.getFormatter(), context);
230     FormatInstructions formatdata = getFormatInstructions(method, fieldAnno);
231     Object valueObject;
232     try {
233       valueObject = method.invoke(fixedFormatRecord);
234     } catch (Exception e) {
235       throw new FixedFormatException(format("could not invoke method %s.%s(%s)", fixedFormatRecord.getClass().getName(), method.getName(), datatype), e);
236     }
237     result = formatter.format(valueObject, formatdata);
238     if (LOG.isDebugEnabled()) {
239       LOG.debug(format("exported %s ", result));
240     }
241     return result;
242   }
243 
244   private String stripMethodPrefix(String name) {
245 
246     if (name.startsWith("get") || name.startsWith("set")) {
247     return name.substring(3);
248     } else if (name.startsWith("is")) {
249       return name.substring(2);
250     } else {
251       return name;
252     }
253 
254   }
255 
256 
257   private FormatContext getFormatContext(Class datatype, Field fieldAnno) {
258     FormatContext context = null;
259     if (fieldAnno != null) {
260       context = new FormatContext(fieldAnno.offset(), datatype, fieldAnno.formatter());
261     }
262     return context;
263 
264   }
265 
266   private FormatInstructions getFormatInstructions(Method method, Field fieldAnno) {
267     FixedFormatPatternData patternData = getFixedFormatPatternData(method.getAnnotation(FixedFormatPattern.class));
268     FixedFormatBooleanData booleanData = getFixedFormatBooleanData(method.getAnnotation(FixedFormatBoolean.class));
269     FixedFormatNumberData numberData = getFixedFormatNumberData(method.getAnnotation(FixedFormatNumber.class));
270     FixedFormatDecimalData decimalData = getFixedFormatDecimalData(method.getAnnotation(FixedFormatDecimal.class));
271     return new FormatInstructions(fieldAnno.length(), fieldAnno.align(), fieldAnno.paddingChar(), patternData, booleanData, numberData, decimalData);
272   }
273 
274   private FixedFormatPatternData getFixedFormatPatternData(FixedFormatPattern annotation) {
275     FixedFormatPatternData result;
276     if (annotation != null) {
277       result = new FixedFormatPatternData(annotation.value());
278     } else {
279       result = FixedFormatPatternData.DEFAULT;
280     }
281     return result;
282   }
283 
284   private FixedFormatBooleanData getFixedFormatBooleanData(FixedFormatBoolean annotation) {
285     FixedFormatBooleanData result;
286     if (annotation != null) {
287       result = new FixedFormatBooleanData(annotation.trueValue(), annotation.falseValue());
288     } else {
289       result = FixedFormatBooleanData.DEFAULT;
290     }
291     return result;
292   }
293 
294   private FixedFormatNumberData getFixedFormatNumberData(FixedFormatNumber annotation) {
295     FixedFormatNumberData result;
296     if (annotation != null) {
297       result = new FixedFormatNumberData(annotation.sign(), annotation.positiveSign(), annotation.negativeSign());
298     } else {
299       result = FixedFormatNumberData.DEFAULT;
300     }
301     return result;
302   }
303 
304   private FixedFormatDecimalData getFixedFormatDecimalData(FixedFormatDecimal annotation) {
305     FixedFormatDecimalData result;
306     if (annotation != null) {
307       result = new FixedFormatDecimalData(annotation.decimals(), annotation.useDecimalDelimiter(), annotation.decimalDelimiter());
308     } else {
309       result = FixedFormatDecimalData.DEFAULT;
310     }
311     return result;
312   }
313 
314   private boolean followsBeanStandard(Method method) {
315     String methodName = method.getName();
316     return methodName.startsWith("get") || methodName.startsWith("is");
317   }
318 }