View Javadoc
1   /*
2    * Copyright 2012 Olivier Godineau
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at http://www.apache.org/licenses/LICENSE-2.0
7    * 
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11   * License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package olg.csv.bean.loader;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.lang.reflect.Method;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import javax.xml.parsers.ParserConfigurationException;
24  import javax.xml.xpath.XPathConstants;
25  import javax.xml.xpath.XPathExpressionException;
26  
27  import olg.csv.base.Cell;
28  import olg.csv.bean.filter.AbstractStringFilter;
29  import olg.csv.bean.formatter.Formatter;
30  import olg.csv.bean.impl.CellProcessor;
31  import olg.csv.bean.impl.PropertyFormatter;
32  import olg.csv.bean.loader.filter.AbstractFiltreLoader;
33  
34  import org.w3c.dom.Document;
35  import org.w3c.dom.Element;
36  import org.w3c.dom.NodeList;
37  import org.xml.sax.SAXException;
38  
39  /**
40   * Class dedicated to load List of ColumnFormatter from an XML file.
41   * 
42   * @param <B>
43   *            the entity type the cellProcessor deals with.
44   * 
45   * @see CellProcessor
46   * 
47   */
48  public class CellProcessorLoader<B> {
49  	/**
50  	 * column name attribute name.
51  	 */
52  	protected static final String ATTR_NAME = "name";
53  	/**
54  	 * class attribute name.
55  	 */
56  	protected static final String ATTR_CLASS = "class";
57  	/**
58  	 * rang atribute name.
59  	 */
60  	protected static final String ATTR_RANG = "rang";
61  	/**
62  	 * Column node name.
63  	 */
64  	protected static final String EL_COLUMN = "column";
65  	/**
66  	 * Formatter node name.
67  	 */
68  	protected static final String EL_FORMATTER = "formatter";
69  	/**
70  	 * Filter node name.
71  	 */
72  	protected static final String EL_FILTER = "filter";
73  	/**
74  	 * Property node name.
75  	 */
76  	protected static final String EL_PROPERTY = "property";
77  	/**
78  	 * Properties node name.
79  	 */
80  	protected static final String EL_PROPERTIES = "properties";
81  
82  	/**
83  	 * Returns the ColumnFormatter list described in the given XML file.
84  	 * 
85  	 * <p>
86  	 * XML root of the given file must be one of the followings :
87  	 * <ul>
88  	 * <li>bean-writer conform to BeanWriterType</li>
89  	 * <li>row-bean and conform to RowBeanType.</li>
90  	 * </ul>
91  	 * </p>
92  	 * 
93  	 * @param file
94  	 *            the XML File.
95  	 * @return a new ColumnFormatter list
96  	 * @throws LoadException
97  	 *             if an error occurs during loading
98  	 * @see CellProcessor
99  	 * 
100 	 */
101 	public List<CellProcessor<B>> load(File file) throws LoadException {
102 		if (file == null) {
103 			throw new IllegalArgumentException("file argument must be not null");
104 		}
105 		List<CellProcessor<B>> retour = null;
106 		Document doc;
107 		try {
108 			Util.validate(file);
109 			doc = Util.openDocument(file);
110 			Element element = (Element) Util.evaluerDOM(doc, "/bean-writer", XPathConstants.NODE);
111 
112 			if (element != null) { // NOPMD by olivier on 07/02/12 01:34
113 				@SuppressWarnings("unchecked")
114 				Class<B> beanClass = (Class<B>) Class.forName(element.getAttribute(ATTR_CLASS));
115 				if (!Util.checkBean(beanClass)) {
116 					throw new LoadException(beanClass + " should be a bean class");
117 				}
118 				retour = load(element, EL_COLUMN, beanClass);
119 
120 			} else {
121 				element = (Element) Util.evaluerDOM(doc, "/bean-row", XPathConstants.NODE);
122 				if (element == null) {
123 					throw new LoadException("Root XML Element must be bean-writer or row-bean to create a Bean Writer");
124 
125 				} else {
126 					@SuppressWarnings("unchecked")
127 					Class<B> beanClass = (Class<B>) Class.forName(element.getAttribute(ATTR_CLASS));
128 					if (!Util.checkBean(beanClass)) {
129 						throw new LoadException(beanClass + " should be a bean class");
130 					}
131 					retour = new RowBeanPropertyFormatterLoader<B>().load(element, EL_PROPERTY, beanClass);
132 				}
133 
134 			}
135 
136 		} catch (ParserConfigurationException e) {
137 			throw new LoadException(e);
138 		} catch (SAXException e) {
139 			throw new LoadException(e);
140 		} catch (IOException e) {
141 			throw new LoadException(e);
142 		} catch (XPathExpressionException e) {
143 			throw new LoadException(e);
144 		} catch (ClassNotFoundException e) {
145 			throw new LoadException(e);
146 		}
147 		return retour;
148 
149 	}
150 
151 	// /**
152 	// * load a CellProcessor list from an xml node
153 	// *
154 	// * @param element
155 	// * xml element conformed to XML Schema bean-writerType or beanRowType
156 	// * specification.
157 	// * @param xmlListNodeName
158 	// * node name identifying columnFormatter description as expectected in the
159 	// XML schema type of the given element
160 	// * @return a new ColumnFormatter list
161 	// * @throws ClassNotFoundException
162 	// * @throws LoadException
163 	// * @throws XPathExpressionException
164 	// */
165 	// protected List<CellProcessor<?>> load(Element element, String
166 	// xmlListNodeName) throws ClassNotFoundException,
167 	// LoadException, XPathExpressionException {
168 	// Class<?> beanClass = Class.forName(element.getAttribute(ATTR_CLASS));
169 	// if (!Util.checkBean(beanClass)) {
170 	// throw new LoadException(beanClass + " should be a bean class");
171 	// }
172 	//
173 	// NodeList liste = (NodeList) Util.evaluerDOM(element, xmlListNodeName,
174 	// XPathConstants.NODESET);
175 	// List<CellProcessor<?>> formatters = new ArrayList<CellProcessor<?>>();
176 	//
177 	// for (int i = 0; i < liste.getLength(); i++) {
178 	// formatters.addAll(extractColumnType((Element) liste.item(i), beanClass));
179 	// }
180 	// Collections.sort(formatters);
181 	// return formatters;
182 	// }
183 
184 	/**
185 	 * load a CellProcessor list from an xml node.
186 	 * 
187 	 * @param element
188 	 *            xml element conformed to XML Schema bean-writerType or
189 	 *            beanRowType specification.
190 	 * @param xmlListNodeName
191 	 *            node name identifying columnFormatter description as
192 	 *            expectected in the XML schema type of the given element
193 	 * @param beanClass
194 	 *            the bean class from which identify the properties targeted by
195 	 *            the cell processors.. May be the B class or the class of a B
196 	 *            property.
197 	 * @param <E>
198 	 *            the type on which identify the properties target of the cell
199 	 *            processors.
200 	 * @return a new ColumnFormatter list
201 	 * 
202 	 * @throws ClassNotFoundException
203 	 *             if class not found when an attribute ("class") is identified
204 	 *             as a class
205 	 * @throws LoadException
206 	 *             if an error occurs during loading
207 	 * @throws XPathExpressionException
208 	 *             on invalid XPathExpression
209 	 */
210 	protected <E> List<CellProcessor<B>> load(Element element, String xmlListNodeName, Class<E> beanClass)
211 			throws ClassNotFoundException, LoadException, XPathExpressionException {
212 
213 		NodeList liste = (NodeList) Util.evaluerDOM(element, xmlListNodeName, XPathConstants.NODESET);
214 		List<CellProcessor<B>> cellProcessors = new ArrayList<CellProcessor<B>>();
215 
216 		for (int i = 0; i < liste.getLength(); i++) {
217 			cellProcessors.addAll(extractColumnType((Element) liste.item(i), beanClass));
218 		}
219 		Collections.sort(cellProcessors);
220 		return cellProcessors;
221 	}
222 	/**
223 	 * Identifies from an XML Element a CellProcessor list.
224 	 * 
225 	 * @param element
226 	 *            the XML Element (ColumnType Element)
227 	 * @param beanClass
228 	 *            the bean class from which identify the properties targeted by
229 	 *            the cell processors. May be the B class or the class of a B
230 	 *            property.
231 	 * @param <E>
232 	 *            the type on which identify the properties target of the cell
233 	 *            processors.
234 	 * @return a list
235 	 * @throws XPathExpressionException
236 	 *             on invalid XPathExpression
237 	 * @throws LoadException
238 	 *             if an error occurs during loading
239 	 * @throws XPathExpressionException
240 	 *             on invalid XPathExpression
241 	 * @throws ClassNotFoundException
242 	 *             if class not found when an attribute ("class") is identified
243 	 *             as a class
244 	 */
245 	protected <E> List<CellProcessor<B>> extractColumnType(Element element, Class<E> beanClass) throws LoadException,
246 			XPathExpressionException, ClassNotFoundException {
247 		List<CellProcessor<B>> formatters = new ArrayList<CellProcessor<B>>(1);
248 		String name = Util.emptyToNull(element.getAttribute(ATTR_NAME));
249 		String rang = element.getAttribute(ATTR_RANG);
250 
251 		name = (name == null ? Cell.defaultCellName(rang) : name); // NOPMD by
252 																	// olivier
253 																	// on
254 		// 07/02/12 01:29
255 		formatters.add(new CellProcessor<B>(rang, name, extractConcateReadPropertyType(
256 				(Element) Util.evaluerDOM(element, EL_PROPERTIES, XPathConstants.NODE), beanClass)));
257 		return formatters;
258 	}
259 
260 	/**
261 	 * Returns a PropertyFormatter which assembles severals properties of the
262 	 * given bean as a string.
263 	 * 
264 	 * @param element
265 	 *            XML node supposed to be conformed to ConcateReadPropertyType
266 	 *            xsd declaration
267 	 * @param beanClass
268 	 *            class of the bean which has the propeties the
269 	 *            propertyFormatter assembles.
270 	 * @return the property formatter loaded from the XML node.
271 	 * @throws LoadException
272 	 *             if an error occurs during loading
273 	 * @throws XPathExpressionException
274 	 *             on invalid XPathExpression
275 	 * @throws ClassNotFoundException
276 	 *             if class not found when an attribute ("class") is identified
277 	 *             as a class
278 	 */
279 
280 	private static PropertyFormatter extractConcateReadPropertyType(Element element, Class<?> beanClass)
281 			throws LoadException, XPathExpressionException, ClassNotFoundException {
282 
283 		NodeList liste = (NodeList) Util.evaluerDOM(element, EL_PROPERTY, XPathConstants.NODESET);
284 		List<PropertyFormatter> formatters = new ArrayList<PropertyFormatter>();
285 		for (int i = 0; i < liste.getLength(); i++) {
286 			formatters.add(extractReadPropertyType((Element) liste.item(i), beanClass));
287 		}
288 
289 		AbstractStringFilter filtre = AbstractFiltreLoader.getInstance().getFilter(
290 				(Element) Util.evaluerDOM(element, EL_FILTER, XPathConstants.NODE));
291 
292 		return PropertyFormatter.getConcatePropertyReader(formatters, filtre);
293 
294 	}
295 
296 	/**
297 	 * Returns PropertyFormatter from the given XML element.
298 	 * 
299 	 * @param element
300 	 *            corresponding to ReadPropertyType xsd declaration
301 	 * @param classBean
302 	 *            bean parent class the bean class from which the returned
303 	 *            propertyFormatter must format a property as a string
304 	 * @return the property formatter loaded from the xml node.
305 	 * @throws LoadException
306 	 *             if an error occurs during loading
307 	 * @throws XPathExpressionException
308 	 *             on invalid XPathExpression
309 	 * @throws ClassNotFoundException
310 	 *             if class not found when an attribute ("class") is identified
311 	 *             as a class
312 	 */
313 
314 	private static PropertyFormatter extractReadPropertyType(Element element, Class<?> classBean) throws LoadException,
315 			XPathExpressionException, ClassNotFoundException {
316 		PropertyFormatter retour = null;
317 		String name = element.getAttribute(ATTR_NAME); // Property name
318 		Element delegateElement = (Element) Util.evaluerDOM(element, EL_PROPERTIES, XPathConstants.NODE);
319 		Method method = olg.csv.bean.Util.identifyGetter(classBean, name);
320 		if (method == null) {
321 			throw new LoadException("Unrecognized property " + name + " from " + classBean);
322 		}
323 
324 		if (delegateElement != null) {
325 			String className = Util.emptyToNull(element.getAttribute(ATTR_CLASS));
326 			Class<?> propertyClass = (className != null ? Class.forName(className) : method.getReturnType()); // NOPMD
327 																												// by
328 																												// olivier
329 																												// on
330 																												// 07/02/12
331 																												// 01:34
332 			// TODO vérifier conformitée propertyClass avec method.getReturnType
333 			PropertyFormatter propertyFormatter = extractConcateReadPropertyType(delegateElement, propertyClass);
334 			retour = PropertyFormatter.getDelegatePropertyReader(method, name, propertyFormatter);
335 
336 		} else {
337 			AbstractStringFilter filtre = AbstractFiltreLoader.getInstance().getFilter(
338 					(Element) Util.evaluerDOM(element, EL_FILTER, XPathConstants.NODE));
339 			Formatter<?> formatter = AbstractFormatterLoader.getInstance().getFormatter(
340 					(Element) Util.evaluerDOM(element, EL_FORMATTER, XPathConstants.NODE));
341 			retour = PropertyFormatter.getPropertyReader(method, name, formatter, filtre);
342 		}
343 		return retour;
344 
345 	}
346 
347 	/**
348 	 * PropertyFormatterLoader adaptation in order to manage with beanRowType
349 	 * elements.
350 	 * 
351 	 */
352 	protected static final class RowBeanPropertyFormatterLoader<B> extends CellProcessorLoader<B> {
353 
354 		/**
355 		 * Checking the order of columns.
356 		 * 
357 		 * @param columnFormatters
358 		 *            CellProcessor list to check.
359 		 * @param <E>
360 		 *            the type the cell processors deals with
361 		 * @throws LoadException
362 		 *             if the columns do not follow the "rang" order
363 		 */
364 		protected <E> void checkLoading(List<CellProcessor<E>> columnFormatters) throws LoadException {
365 			int previousRank = -1;
366 			int expectedRank = 0;
367 			List<String> propertyNames = new ArrayList<String>();
368 			for (CellProcessor<E> columnFormatter : columnFormatters) {
369 				if (propertyNames.contains(columnFormatter.getPropertyFormatter().getFullName())) {
370 					throw new LoadException("the property " + columnFormatter.getPropertyFormatter().getFullName()
371 							+ " is still mapped");
372 				} else {
373 					propertyNames.add(columnFormatter.getPropertyFormatter().getFullName());
374 				}
375 				if (columnFormatter.getRang() == expectedRank) {
376 					previousRank = expectedRank++;
377 				} else {
378 					if (previousRank == -1) {
379 						throw new LoadException("The first column is missing at rank 0");
380 
381 					} else {
382 						throw new LoadException("A column is missing after column at rank " + previousRank);
383 					}
384 				}
385 			}
386 		}
387 
388 		@Override
389 		protected <E> List<CellProcessor<B>> load(Element element, String xmlListNodeName, Class<E> beanClass)
390 				throws ClassNotFoundException, LoadException, XPathExpressionException {
391 
392 			List<CellProcessor<B>> columnFormatters = super.load(element, xmlListNodeName, beanClass);
393 			checkLoading(columnFormatters);
394 			return columnFormatters;
395 
396 		}
397 
398 		/**
399 		 * @param element
400 		 *            corresponding to BeanRowPropertyType xsd declaration
401 		 *            {@inheritDoc}
402 		 */
403 		@Override
404 		protected <T> List<CellProcessor<B>> extractColumnType(Element element, Class<T> classBean)
405 				throws LoadException, XPathExpressionException, ClassNotFoundException {
406 			List<CellProcessor<B>> cellProcessors = new ArrayList<CellProcessor<B>>();
407 			String name = element.getAttribute(ATTR_NAME); // Property name
408 
409 			Method method = olg.csv.bean.Util.identifyGetter(classBean, name);
410 			if (method == null) {
411 				throw new LoadException("No recognized property " + name + " of " + classBean);
412 			}
413 
414 			NodeList liste = (NodeList) Util.evaluerDOM(element, EL_PROPERTY, XPathConstants.NODESET);
415 
416 			if (liste.getLength() > 0) {
417 				String className = Util.emptyToNull(element.getAttribute(ATTR_CLASS));
418 				Class<?> propertyClass = (Class<?>) (className != null ? Class.forName(className) : method
419 						.getReturnType()); // NOPMD
420 				// by
421 				// olivier
422 				// on
423 				// 07/02/12
424 				// 01:33
425 
426 				for (int i = 0; i < liste.getLength(); i++) {
427 					List<CellProcessor<B>> delegatedProcessors = extractColumnType((Element) liste.item(i),
428 							propertyClass);
429 
430 					for (CellProcessor<B> delegatedProcessor : delegatedProcessors) {
431 						delegatedProcessor.setPropertyFormatter(PropertyFormatter.getDelegatePropertyReader(method,
432 								name, delegatedProcessor.getPropertyFormatter()));
433 
434 						cellProcessors.add(delegatedProcessor);
435 					}
436 
437 				}
438 
439 			} else {
440 				Formatter<?> formatter = AbstractFormatterLoader.getInstance().getFormatter(
441 						(Element) Util.evaluerDOM(element, EL_FORMATTER, XPathConstants.NODE));
442 
443 				String rang = ((Element) Util.evaluerDOM(element, EL_COLUMN, XPathConstants.NODE))
444 						.getAttribute(ATTR_RANG);
445 				String columnName = Util.emptyToNull(((Element) Util
446 						.evaluerDOM(element, EL_COLUMN, XPathConstants.NODE)).getAttribute(ATTR_NAME));
447 				CellProcessor<B> columnFormatter = new CellProcessor<B>(rang, columnName == null ? name : columnName,
448 						PropertyFormatter.getPropertyReader(method, name, formatter, null));
449 
450 				cellProcessors.add(columnFormatter);
451 			}
452 			return cellProcessors;
453 
454 		}
455 
456 	}
457 
458 }