PageRenderTime 61ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/source/java/ee/webmedia/alfresco/importer/excel/mapper/ExcelRowMapper.java

https://bitbucket.org/smitdevel/delta
Java | 341 lines | 299 code | 22 blank | 20 comment | 92 complexity | 359b2c5791033d6f4ba37ca43a466bad MD5 | raw file
  1. package ee.webmedia.alfresco.importer.excel.mapper;
  2. import java.io.File;
  3. import java.lang.reflect.Field;
  4. import java.text.DateFormat;
  5. import java.text.ParseException;
  6. import java.text.SimpleDateFormat;
  7. import java.util.ArrayList;
  8. import java.util.Arrays;
  9. import java.util.Date;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. import java.util.Map.Entry;
  13. import java.util.regex.Matcher;
  14. import java.util.regex.Pattern;
  15. import org.apache.commons.lang.StringUtils;
  16. import org.apache.poi.hssf.usermodel.HSSFCell;
  17. import org.apache.poi.hssf.usermodel.HSSFRichTextString;
  18. import org.apache.poi.ss.formula.eval.ErrorEval;
  19. import org.apache.poi.ss.usermodel.Cell;
  20. import org.apache.poi.ss.usermodel.Row;
  21. import org.apache.poi.ss.usermodel.Sheet;
  22. import org.apache.poi.ss.usermodel.Workbook;
  23. import org.springframework.util.Assert;
  24. public abstract class ExcelRowMapper<G> {
  25. private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ExcelRowMapper.class);
  26. private static final String letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  27. private static final DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy");
  28. private static final String dateSeparator = "\\.";
  29. /**
  30. * Patter to match first date inside input text
  31. */
  32. private static final Pattern dateMatcherPattern = Pattern.compile(
  33. "(^|.*?\\D)" + // before date: either only the start of the input or char-sequence that is not ending with a digit
  34. "(" + // start capturing group
  35. "(0[1-9]|[1-9]|[12][0-9]|3[01])" + // day part of the date
  36. dateSeparator + //
  37. "(0[1-9]|1[012]|[1-9])" + // month part of the date
  38. dateSeparator + //
  39. "\\d{4}" + // year part of the date
  40. ")" + // end capturing group
  41. "($|\\D.*)" // after date: either only the end of input or char-sequence that doesn't start with a digit
  42. );
  43. public abstract G mapRow(Row row, long rowNr, File excelFile, String string);
  44. public void setMapperContext(@SuppressWarnings("unused") Map<String, Object> mapperContext) {
  45. // could be overridden in subclassses
  46. }
  47. protected String get(Row row, int colIndex) {
  48. return StringUtils.trimToNull(get(row, colIndex, String.class));
  49. }
  50. /**
  51. * @param row
  52. * @param colIndex
  53. * @return date value of cell or if cell is not date try to extract date from text. If text contains no date, null is returned.
  54. */
  55. protected Date getDate(Row row, int colIndex) {
  56. try {
  57. return get(row, colIndex, Date.class);
  58. } catch (FieldMismatchException e) {
  59. // final String cellTextValue = getCellValue(cell);
  60. final String cellTextValue = get(row, colIndex, String.class);
  61. final Date extractedDate = extractDate(cellTextValue);
  62. if (extractedDate == null) {
  63. final FieldMismatchException fieldMismatchException = new FieldMismatchException("Also failed to extract date from cell textvalue '"
  64. + cellTextValue + "'", e);
  65. fieldMismatchException.setColumnIndex(colIndex);
  66. throw fieldMismatchException;
  67. }
  68. if (log.isTraceEnabled()) {
  69. log.trace((row.getRowNum() + 1) + ". row " + (colIndex + 1) + ". column is expected to be Date, but is text '" //
  70. + cellTextValue + "'. Using extracted date from text: " + extractedDate);
  71. }
  72. return extractedDate;
  73. }
  74. }
  75. protected <T> T get(Row row, int colIndex, Class<T> clazz) {
  76. final Cell cell = row.getCell(colIndex);
  77. if (cell == null) {
  78. return null;
  79. }
  80. if (clazz == null || clazz == String.class) {
  81. final int cellType = cell.getCellType();
  82. if (cellType == Cell.CELL_TYPE_STRING) {
  83. @SuppressWarnings("unchecked")
  84. T res = (T) cell.getStringCellValue();
  85. return res;
  86. } else if (cellType == Cell.CELL_TYPE_BLANK) {
  87. return null;
  88. } else if (cellType == Cell.CELL_TYPE_NUMERIC) {
  89. final double numericCellValue = cell.getNumericCellValue();
  90. final long longValue = (long) numericCellValue;
  91. @SuppressWarnings("unchecked")
  92. final T stringVal = (T) ((longValue == numericCellValue) ? String.valueOf(longValue) : Double.valueOf(numericCellValue).toString());
  93. return stringVal;
  94. } else if (cellType == Cell.CELL_TYPE_BOOLEAN) {
  95. @SuppressWarnings("unchecked")
  96. T res = (T) Boolean.valueOf(cell.getBooleanCellValue()).toString();
  97. return res;
  98. } else if (cellType == Cell.CELL_TYPE_FORMULA) {
  99. throw new RuntimeException("cell with formula: " + cell.getCellFormula());
  100. } else if (cellType == Cell.CELL_TYPE_ERROR) {
  101. throw new RuntimeException("cell with error: " + cell);
  102. }
  103. // } else {
  104. } else if (clazz == Date.class) {
  105. Date dateCellValue;
  106. try {
  107. dateCellValue = cell.getDateCellValue();
  108. } catch (Exception e) {
  109. final FieldMismatchException fieldMismatchException = new FieldMismatchException("Can't get " + clazz + " value from " + (colIndex + 1)
  110. + ". column with type "
  111. + getCellTypeName(cell.getCellType())
  112. + ". Cell value:\n'" + getCellValue(cell) + "'", e);
  113. fieldMismatchException.setColumnIndex(colIndex);
  114. throw fieldMismatchException;
  115. }
  116. @SuppressWarnings("unchecked")
  117. T result = (T) dateCellValue;
  118. return result;
  119. }
  120. throw new RuntimeException("unimplemented");
  121. }
  122. private Object getCellValue(final Cell cell) {
  123. final Object value;
  124. final int cellType = cell.getCellType();
  125. if (cellType == Cell.CELL_TYPE_STRING) {
  126. value = cell.getStringCellValue();
  127. } else if (cellType == Cell.CELL_TYPE_BLANK) {
  128. value = null;
  129. } else if (cellType == Cell.CELL_TYPE_NUMERIC) {
  130. value = cell.getNumericCellValue();
  131. } else if (cellType == Cell.CELL_TYPE_BOOLEAN) {
  132. value = cell.getBooleanCellValue();
  133. } else if (cellType == Cell.CELL_TYPE_FORMULA) {
  134. value = evaluateFormula(cell);
  135. } else if (cellType == Cell.CELL_TYPE_ERROR) {
  136. // throw new FieldMismatchException("cell with error: " + cell);
  137. return cell.toString();
  138. } else {
  139. throw new RuntimeException("unknown cell type: " + cellType + "; cell:\n" + cell);
  140. }
  141. return value;
  142. }
  143. protected Date extractDate(String textContainingDate) {
  144. if (StringUtils.isNotBlank(textContainingDate)) {
  145. Matcher matcher = dateMatcherPattern.matcher(textContainingDate.trim());
  146. if (matcher.find()) {
  147. final String dateString = matcher.group(2);
  148. if (StringUtils.isNotBlank(dateString)) {
  149. try {
  150. final Date parse = dateFormat.parse(dateString);
  151. return parse;
  152. } catch (ParseException e) {
  153. final String msg = "Failed to parse '" + dateString + "' to Date";
  154. log.error(msg, e);
  155. throw new RuntimeException(msg, e);
  156. }
  157. }
  158. }
  159. }
  160. return null;
  161. }
  162. private Object evaluateFormula(final Cell cell) {
  163. if (cell instanceof HSSFCell) {
  164. final int cachedFormulaResultType = cell.getCachedFormulaResultType();
  165. switch (cachedFormulaResultType) {
  166. case Cell.CELL_TYPE_STRING:
  167. StringBuffer text = new StringBuffer();
  168. HSSFRichTextString str = ((HSSFCell) cell).getRichStringCellValue();
  169. if (str != null && str.length() > 0) {
  170. text.append(str.toString());
  171. }
  172. return text.toString();
  173. case Cell.CELL_TYPE_NUMERIC:
  174. return cell.getNumericCellValue();
  175. case Cell.CELL_TYPE_BOOLEAN:
  176. return cell.getBooleanCellValue();
  177. case Cell.CELL_TYPE_ERROR:
  178. return ErrorEval.getText(cell.getErrorCellValue());
  179. default:
  180. throw new RuntimeException("Unimplemented reading formula from cell with formula type " + cachedFormulaResultType);
  181. }
  182. }
  183. throw new RuntimeException("Unimplemented reading formula from cell with class " + cell.getClass());
  184. }
  185. /**
  186. * Method copied from private method {@link HSSFCell#getCellType()}
  187. */
  188. private static String getCellTypeName(int cellTypeCode) {
  189. switch (cellTypeCode) {
  190. case Cell.CELL_TYPE_BLANK:
  191. return "blank";
  192. case Cell.CELL_TYPE_STRING:
  193. return "text";
  194. case Cell.CELL_TYPE_BOOLEAN:
  195. return "boolean";
  196. case Cell.CELL_TYPE_ERROR:
  197. return "error";
  198. case Cell.CELL_TYPE_NUMERIC:
  199. return "numeric";
  200. case Cell.CELL_TYPE_FORMULA:
  201. return "formula";
  202. }
  203. return "#unknown cell type (" + cellTypeCode + ")#";
  204. }
  205. public void setExcelColumnsFromAnnotations() {
  206. final HashMap<String/* fieldName */, Integer/* colNr */> fieldToColNrMap = new HashMap<String, Integer>();
  207. getFields(this, this.getClass(), fieldToColNrMap);
  208. }
  209. private void getFields(Object object, final Class<?> clazz, HashMap<String, Integer> fieldToColNrMap) {
  210. final Field[] fields = clazz.getDeclaredFields();
  211. for (final Field field : fields) {
  212. final ExcelColumn excelCol = field.getAnnotation(ExcelColumn.class);
  213. if (excelCol != null) {
  214. if (field.getType() != Integer.class) {
  215. throw new RuntimeException("Field not integer");// at the moment only Integers are supported
  216. }
  217. try {
  218. Integer colNr = null;
  219. final String fieldName = field.getName();
  220. if (!fieldToColNrMap.containsKey(fieldName)) {
  221. final char colLetter = excelCol.value();
  222. Arrays.asList(letters);
  223. if ('-' == colLetter) {
  224. colNr = excelCol.colNr();
  225. } else {
  226. colNr = letters.indexOf(colLetter);
  227. }
  228. if (colNr == Integer.MIN_VALUE) {
  229. colNr = null; // default value of annotation can't be null, using Integer.MIN_VALUE to denote it
  230. }
  231. fieldToColNrMap.put(fieldName, colNr);
  232. } else {
  233. colNr = fieldToColNrMap.get(fieldName); // prefer annotation value of the corresponding filed from childClass
  234. }
  235. Object fieldValBefore = null;
  236. field.setAccessible(true);
  237. if (log.isDebugEnabled()) {
  238. fieldValBefore = field.get(object);
  239. }
  240. field.set(object, colNr);
  241. if (log.isTraceEnabled()) {
  242. Object fieldValAfter = field.get(object);
  243. if ((colNr == null) != (fieldValAfter == null)) {
  244. Assert.isTrue(false, "Expected that the value after setting field is equal to value set to field");
  245. }
  246. if (fieldValAfter != null) {
  247. Assert.isTrue(fieldValAfter.equals(colNr), "Expected that the value after setting field is equal to value set to field");
  248. }
  249. log.trace("Setting value of field '" + field + "' based on @ExcelColumn from '" + fieldValBefore + "' to '" + colNr + "'");
  250. }
  251. } catch (IllegalArgumentException e) {
  252. throw new RuntimeException("failed ", e);
  253. } catch (IllegalAccessException e) {
  254. throw new RuntimeException("failed to access filed " + field, e);
  255. } catch (SecurityException e) {
  256. throw new RuntimeException("failed to get field - no access", e);
  257. // } catch (NoSuchFieldException e) {
  258. // throw new RuntimeException("failed to get field - no field", e);
  259. }
  260. }
  261. }
  262. final Class<?> superclass = clazz.getSuperclass();
  263. if (superclass != null) {
  264. setFields(object, superclass, fieldToColNrMap);
  265. getFields(object, superclass, fieldToColNrMap);
  266. }
  267. }
  268. private void setFields(Object object, Class<?> clazz, HashMap<String, Integer> fieldToColNrMap) {
  269. for (Entry<String, Integer> entry : fieldToColNrMap.entrySet()) {
  270. try {
  271. final Field field = clazz.getDeclaredField(entry.getKey());
  272. final ExcelColumn excelCol = field.getAnnotation(ExcelColumn.class);
  273. if (excelCol != null) { // only overwrite values of fields in parentClass if field is annotated
  274. field.setAccessible(true);
  275. field.set(object, entry.getValue());
  276. }
  277. } catch (SecurityException e) {
  278. throw new RuntimeException("Failed", e);
  279. } catch (NoSuchFieldException e) {
  280. // ignore
  281. } catch (IllegalArgumentException e) {
  282. throw new RuntimeException("Failed to set", e);
  283. } catch (IllegalAccessException e) {
  284. throw new RuntimeException("Failed to set", e);
  285. }
  286. }
  287. }
  288. public static class SheetFinder {
  289. private String[] sheetNames;
  290. public SheetFinder(String... sheetNames) {
  291. if (sheetNames == null || sheetNames.length == 0) {
  292. throw new RuntimeException("At least one sheet name is required");
  293. }
  294. this.sheetNames = sheetNames;
  295. }
  296. /** find from all sheets */
  297. public SheetFinder() {
  298. // find from all sheets
  299. }
  300. public ArrayList<Sheet> findSheetsToImport(Workbook wb) {
  301. final ArrayList<Sheet> sheets = new ArrayList<Sheet>();
  302. if (sheetNames == null) {
  303. final int numberOfSheets = wb.getNumberOfSheets();
  304. for (int i = 0; i < numberOfSheets; i++) {
  305. sheets.add(wb.getSheetAt(i));
  306. }
  307. } else {
  308. for (String sheet : sheetNames) {
  309. sheets.add(wb.getSheet(sheet));
  310. }
  311. }
  312. return sheets;
  313. }
  314. @Override
  315. public String toString() {
  316. return sheetNames != null ? Arrays.asList(sheetNames).toString() : "ALL SHEETS";
  317. }
  318. }
  319. }