Java tutorial
/* SpecRunner - Acceptance Test Driven Development Tool Copyright (C) 2011-2018 Thiago Santos This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/> */ package org.specrunner.plugins.core.objects; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.apache.commons.beanutils.PropertyUtils; import org.specrunner.SRServices; import org.specrunner.context.IContext; import org.specrunner.parameters.DontEval; import org.specrunner.parameters.core.UtilParametrized; import org.specrunner.plugins.PluginException; import org.specrunner.plugins.core.AbstractPluginTable; import org.specrunner.result.IResultSet; import org.specrunner.result.status.Failure; import org.specrunner.result.status.Success; import org.specrunner.result.status.Warning; import org.specrunner.source.ISource; import org.specrunner.source.ISourceFactoryManager; import org.specrunner.source.SourceException; import org.specrunner.util.UtilLog; import org.specrunner.util.expression.UtilExpression; import org.specrunner.util.string.UtilString; import org.specrunner.util.xom.UtilNode; import org.specrunner.util.xom.node.CellAdapter; import org.specrunner.util.xom.node.INodeHolder; import org.specrunner.util.xom.node.INodeHolderFactory; import org.specrunner.util.xom.node.RowAdapter; import org.specrunner.util.xom.node.TableAdapter; import org.specrunner.util.xom.node.UtilTable; import nu.xom.Attribute; import nu.xom.Document; import nu.xom.Element; import nu.xom.Node; import nu.xom.Nodes; /** * Generic object plugin. To write object plugins override method * <code>isMapped()</code> and <code>action(...)</code>. i.e. * SpecRunner-Hibernate3 extends the object manipulation to save/update/delete * data using Hibernate3 infra-structure. * * @author Thiago Santos * */ public abstract class AbstractPluginObject extends AbstractPluginTable { /** * Super type of object. */ protected String supermapping; /** * Object class name. */ protected String type; /** * Object class. */ protected Class<?> typeInstance; /** * Object creator name. */ protected String creator; /** * Object creator instance. */ protected IObjectCreator creatorInstance; /** * The identification fields. */ protected String reference; /** * The identification fields list. */ protected List<String> references = new LinkedList<String>(); /** * Separator of identification attributes. */ protected String separator; /** * The map of generic fields to be used as reference. */ protected String mapping; /** * List of header definition fields. */ protected Map<String, Field> headerToFields = new HashMap<String, Field>(); /** * List of field definition fields. */ protected Map<String, Field> fieldToFields = new HashMap<String, Field>(); /** * List of fields. */ protected List<Field> fields = new LinkedList<Field>(); /** * Flag if the created objects are expected to be mapped in memory by * default. */ protected boolean mapped = false; /** * Mapping of key before processing to the ones after processing. */ protected Map<String, String> keysBefore = new HashMap<String, String>(); /** * Mapping of identifiers to object instances. */ protected Map<String, Object> instances = new HashMap<String, Object>(); /** * Get super class mapping. * * @return A super class mapping. */ public String getSupermapping() { return supermapping; } /** * Set mapping for superclass object. * * @param supermapping * A super mapping. */ public void setSupermapping(String supermapping) { this.supermapping = supermapping; } /** * The object type of the plugin. i.e. * <code>type='system.entity.Person'</code>. * * @return The type. */ public String getType() { return type; } /** * Set the type. * * @param type * A new type. */ public void setType(String type) { this.type = type; } /** * Gets the corresponding class to type. * * @return The class. */ public Class<?> getTypeInstance() { return typeInstance; } /** * Sets the instance type. * * @param typeInstance * A new type. */ public void setTypeInstance(Class<?> typeInstance) { this.typeInstance = typeInstance; } /** * Returns the object creator of embeddable objects. * * @return The creator class name. */ public String getCreator() { return creator; } /** * Sets the creator class. * * @param creator * A new creator class name. */ public void setCreator(String creator) { this.creator = creator; } /** * Object creator type. * * @return The creator instance. */ public IObjectCreator getCreatorInstance() { return creatorInstance; } /** * Sets the creator. * * @param creatorInstance * A new creator instance. */ public void setCreatorInstance(IObjectCreator creatorInstance) { this.creatorInstance = creatorInstance; } /** * Sets the attribute which represents the keys of the entity. These fields * are used to create the instance key. i.e. if * <code>reference='id,name'</code> is used, the 'id' attribute and 'name' * attribute are used as the object key, separated by 'separator' field. * * @return The list of attribute references in the expected order. */ public String getReference() { return reference; } /** * Sets the references. * * @param reference * A new reference list. */ @DontEval public void setReference(String reference) { this.reference = reference; if (reference != null) { references.clear(); String[] refs = reference.split(","); for (String s : refs) { references.add(s.trim()); } } } /** * Gets the string used to separate fields of reference. i.e. if * <code>reference='id,name'</code> and <code>separator='/'</code>, the * object instance corresponding to a given line will be 'id/name'. * * @return The keys separator. */ public String getSeparator() { return separator; } /** * Sets identifier separator. * * @param separator * The separator. */ public void setSeparator(String separator) { this.separator = separator; } /** * Return the map of field information. For example, if there is a mapping * for object City already defined in a file name '/city.html' or * '/city.xml', just add map='/cities.html' to the tag. * * @return The mapping. */ public String getMapping() { return mapping; } /** * Sets the object mapping. * * @param mapping * The mapping. */ public void setMapping(String mapping) { this.mapping = mapping; } @Override public void initialize(IContext context, TableAdapter table) throws PluginException { super.initialize(context, table); if (mapping != null) { loadMapping(context, table, mapping); } else { setObjectInformation(); } } /** * Load mapping with predefined values. * * @param context * The context. * @param table * The table. * @throws PluginException * On mapping errors. */ protected void loadMapping(IContext context, TableAdapter table, String map) throws PluginException { try { if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("Loading object mapping>" + map); } URL file = AbstractPluginObject.class.getResource(map); if (file == null) { throw new PluginException("The object mapping file '" + map + "' not found."); } if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("Loading object mapping file>" + file); } ISource source = SRServices.get(ISourceFactoryManager.class).newSource(file.toString()); Document doc = source.getDocument(); Nodes ns = doc.query("//table"); if (ns.size() == 0) { throw new PluginException("The mapping file must have a table element with fields information."); } Element n = null; for (int i = 0; i < ns.size(); i++) { Element tmp = (Element) ns.get(i); if (!UtilNode.isIgnore(tmp)) { n = tmp; break; } } if (n == null) { throw new PluginException( "The mapping file might have at least one table with a row (usually a header) of generic field information."); } TableAdapter ta = UtilTable.newTable(n); RowAdapter information = null; for (int i = 0; i < ta.getRowCount(); i++) { RowAdapter tmp = ta.getRow(i); if (!UtilNode.isIgnore(tmp.getNode())) { information = tmp; break; } } if (information == null) { throw new PluginException( "The mapping file might have at least one (not ignored) row in a (not ignored) table."); } supermapping = null; // set table properties such as type/separator/id/etc. UtilParametrized.setProperties(context, this, n); if (supermapping != null) { loadMapping(context, table, supermapping); // set table properties such as type/separator/id/etc. UtilParametrized.setProperties(context, this, n); } // to replace specific settings back. UtilParametrized.setProperties(context, this, (Element) table.getNode()); // set type objects. setObjectInformation(); if (isSpecial()) { if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("Primitive types, enums and String are not to be loaded by fields. RECEIVED:" + typeInstance); } return; } // load fields. List<Field> general = new LinkedList<Field>(); loadFields(context, information, general); for (Field field : general) { headerToFields.put(field.getFieldName(), field); fieldToFields.put(field.getFullName(), field); } } catch (SourceException e) { if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info(e.getMessage(), e); } throw new PluginException("Fail on loading mapping information: '" + mapping + "'.", e); } } /** * Set object and/or creator information. * * @throws PluginException * On setting errors. */ protected void setObjectInformation() throws PluginException { try { if (type != null) { typeInstance = Class.forName(type); } if (creator != null) { creatorInstance = (IObjectCreator) Class.forName(creator).newInstance(); } } catch (Exception e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } throw new PluginException(e); } if (typeInstance == null) { throw new PluginException("Set 'type' with object class name."); } } /** * Checks if the instance is primitive, enum or string. * * @return true, if yes, false, otherwise. */ protected boolean isSpecial() { return typeInstance.isPrimitive() || typeInstance.isEnum() || typeInstance == String.class; } @Override public void doEnd(IContext context, IResultSet result, TableAdapter table) throws PluginException { if (isMapped()) { SRServices.getObjectManager().bind(this); } int pos = 0; if (!isSpecial()) { pos = readHeader(context, result, table); } readData(pos, context, result, table); } /** * Read header information. * * @param context * A context. * @param result * A result. * @param table * The table. * @throws PluginException * On read errors. */ protected int readHeader(IContext context, IResultSet result, TableAdapter table) throws PluginException { int index = 0; RowAdapter row = null; for (; index < table.getRowCount(); index++) { RowAdapter tmp = table.getRow(index); if (UtilNode.isIgnore(tmp.getNode())) { continue; } row = tmp; break; } if (row == null) { throw new PluginException("Table should have at least header information."); } try { loadFields(context, row, fields); result.addResult(Success.INSTANCE, context.newBlock(row.getNode(), this)); } catch (Exception e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } result.addResult(Failure.INSTANCE, context.newBlock(row.getNode(), this), e); } return index + 1; } /** * Read data information. * * @param context * A context. * @param result * A result. * @param table * The table. * @throws PluginException * On read errors. */ protected void readData(int readPosition, IContext context, IResultSet result, TableAdapter table) throws PluginException { for (int i = (isSpecial() ? 0 : readPosition); i < table.getRowCount(); i++) { RowAdapter row = table.getRow(i); try { processLine(context, table, row, result); } catch (Exception e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } result.addResult(Failure.INSTANCE, context.newBlock(row.getNode(), this), e); } } } /** * Load fields based on <code>th</code> tags. * * @param context * The context. * @param row * The row. * @param list * List of fields. * @throws PluginException * On load errors. */ protected void loadFields(IContext context, RowAdapter row, List<Field> list) throws PluginException { int index = 0; for (CellAdapter cell : row.getCells()) { String field = cell.getAttribute("feature", cell.getAttribute("field", cell.getAttribute("property", null))); String fieldName = UtilString.getNormalizer().camelCase(cell.getValue(context)); String name = field != null ? (field.endsWith(".") ? field + fieldName : field) : fieldName; Field f = headerToFields.get(fieldName); if (f == null) { f = fieldToFields.get(name); if (f == null) { f = new Field(); f.setFieldName(fieldName); } else { name = f.getFullName(); } } else { name = f.getFullName(); } f.setIndex(index++); f.setReference(references.contains(name)); boolean ignore = f.ignore; if (cell.hasAttribute(UtilNode.IGNORE)) { boolean tmp = Boolean.parseBoolean(cell.getAttribute(UtilNode.IGNORE)); if (tmp != ignore) { ignore = tmp; f.setIgnore(ignore); } } if (cell.hasAttribute(INodeHolder.ATTRIBUTE_EVALUATION)) { boolean eval = Boolean.parseBoolean(cell.getAttribute(INodeHolder.ATTRIBUTE_EVALUATION)); if (eval != f.eval) { f.setEval(eval); } } if (!ignore) { StringTokenizer st = new StringTokenizer(name, "."); String[] names = new String[st.countTokens()]; for (int i = 0; i < names.length; i++) { names[i] = st.nextToken(); } if (names.length > 0) { f.setNames(names); } Class<?>[] types = new Class<?>[names.length]; Class<?> currentType = typeInstance; for (int i = 0; currentType != null && i < types.length; i++) { Method m = null; try { m = currentType.getMethod( "get" + Character.toUpperCase(names[i].charAt(0)) + names[i].substring(1)); } catch (Exception e) { try { m = currentType.getMethod( "is" + Character.toUpperCase(names[i].charAt(0)) + names[i].substring(1)); } catch (Exception e1) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e1.getMessage(), e1); } } } if (m == null) { throw new PluginException( "Getter method for " + names[i] + " not found for type '" + currentType + "'."); } types[i] = m.getReturnType(); currentType = types[i]; } if (types.length > 0) { f.setTypes(types); } String coltype = cell.getAttribute("coltype", null); if (coltype != null) { f.setColtype(coltype); } String def = cell.getAttribute("default", null); if (def != null) { f.setDef(def); } String converter = cell.getAttribute(INodeHolder.ATTRIBUTE_CONVERTER, null); if (converter != null) { f.setConverter(converter); } int i = 0; List<Object> args = new LinkedList<Object>(); while (cell.hasAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_CONVERTER_PREFIX + i)) { args.add(UtilExpression.evaluate( cell.getAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_CONVERTER_PREFIX + i), context, true)); i++; } if (f.getArgs() == null || !args.isEmpty()) { f.setArgs(args.toArray(new String[args.size()])); } String formatter = cell.getAttribute(INodeHolder.ATTRIBUTE_FORMATTER, null); if (formatter != null) { f.setFormatter(formatter); } int j = 0; List<Object> formArgs = new LinkedList<Object>(); while (cell.hasAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_FORMATTER_PREFIX + j)) { formArgs.add(UtilExpression.evaluate( cell.getAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_FORMATTER_PREFIX + j), context, true)); j++; } if (f.getFormattersArgs() == null || !formArgs.isEmpty()) { f.setFormattersArgs(formArgs.toArray(new String[formArgs.size()])); } String comparator = cell.getAttribute(INodeHolder.ATTRIBUTE_COMPARATOR, null); if (comparator != null) { f.setComparator(comparator); } } if (UtilLog.LOG.isInfoEnabled()) { UtilLog.LOG.info("FIELD>" + f); } list.add(f); } } /** * Hold column information. * * @author Thiago Santos * */ public static class Field { /** * Field index. */ private int index; /** * Ignore column field. */ private boolean ignore; /** * Eval column field. Default is true. */ private boolean eval = true; /** * Is reference column. */ private boolean reference; /** * The column name of the field. */ private String fieldName; /** * Field name. Many-level. */ private String[] names; /** * Field types. Many-level. */ private Class<?>[] types; /** * Default value. */ private String def; /** * Collection type. */ private String coltype; /** * Converters. */ private String converter; /** * Arguments for converters. */ private String[] args; /** * Formatters. */ private String formatter; /** * Arguments for formatters. */ private String[] formattersArgs; /** * Comparator name. */ private String comparator; /** * Get index. * * @return The index. */ public int getIndex() { return index; } /** * Sets the index. * * @param index * The index. */ public void setIndex(int index) { this.index = index; } /** * Ignore the column with this mark. * * @return true, to ignore, false, otherwise. */ public boolean isIgnore() { return ignore; } /** * Sets to ignore a column. * * @param ignore * The ignore mark. */ public void setIgnore(boolean ignore) { this.ignore = ignore; } /** * Check if eval is enabled. Default is true. * * @return true, to eval, false,otherwise. */ public boolean isEval() { return eval; } /** * Set eval status. * * @param eval * true, to eval, false, otherwise. */ public void setEval(boolean eval) { this.eval = eval; } /** * Get if the field is a key value. * * @return true, if is part of key, false, otherwise. */ public boolean isReference() { return reference; } /** * Set if the field is a key part. * * @param reference * The reference status. */ public void setReference(boolean reference) { this.reference = reference; } /** * Gets the field column name. * * @return The field column name. */ public String getFieldName() { return fieldName; } /** * The column name. * * @param fieldName * The column name. */ public void setFieldName(String fieldName) { this.fieldName = fieldName; } /** * Gets names. * * @return The names. */ public String[] getNames() { return names; } /** * Sets the name. * * @param names * The names. */ public void setNames(String[] names) { this.names = names == null ? null : Arrays.copyOf(names, names.length); } /** * Get types. * * @return The types. */ public Class<?>[] getTypes() { return types; } /** * Set types. * * @param types * The types. */ public void setTypes(Class<?>[] types) { this.types = types == null ? null : Arrays.copyOf(types, types.length); } /** * Get default value. * * @return The default. */ public String getDef() { return def; } /** * Sets the default. * * @param def * The default. */ public void setDef(String def) { this.def = def; } /** * Get collection type information. * * @return Collection type. */ public String getColtype() { return coltype; } /** * Set collection type. * * @param coltype * The type of collection. */ public void setColtype(String coltype) { this.coltype = coltype; } /** * Get converters. * * @return The converters. */ public String getConverter() { return converter; } /** * Set converter. * * @param converter * The converter. */ public void setConverter(String converter) { this.converter = converter; } /** * The converter arguments. * * @return The arguments. */ public String[] getArgs() { return args; } /** * Set converter arguments. * * @param args * The arguments. */ public void setArgs(String[] args) { this.args = args == null ? null : Arrays.copyOf(args, args.length); } /** * Get formatter. * * @return The formatter. */ public String getFormatter() { return formatter; } /** * Set formatter. * * @param formatter * The formatter. */ public void setFormatter(String formatter) { this.formatter = formatter; } /** * The formatter arguments. * * @return The arguments. */ public String[] getFormattersArgs() { return formattersArgs; } /** * Set formatter arguments. * * @param args * The arguments. */ public void setFormattersArgs(String[] args) { this.formattersArgs = args == null ? null : Arrays.copyOf(args, args.length); } /** * Gets the comparator. * * @return The comparator. */ public String getComparator() { return comparator; } /** * Sets the comparator. * * @param comparator * The comparator. */ public void setComparator(String comparator) { this.comparator = comparator; } /** * Get full name of field. * * @return The name. */ public String getFullName() { StringBuilder strNames = new StringBuilder(""); for (int i = 0; i < names.length; i++) { strNames.append(i == 0 ? "" : "."); strNames.append(names[i]); } return strNames.toString(); } /** * Get the type of field. * * @return The type. */ public Class<?> getSpecificType() { return types[types.length - 1]; } /** * Check if an attribute is a collection. * * @return true, if yes, false, otherwise. */ public boolean isCollection() { return Collection.class.isAssignableFrom(getSpecificType()); } /** * Check if an attribute is an array. * * @return true, if yes, false, otherwise. */ public boolean isArray() { return getSpecificType().isArray(); } @Override public String toString() { StringBuilder strNames = new StringBuilder(""); for (int i = 0; names != null && i < names.length; i++) { strNames.append((i == 0 ? "" : ",") + names[i]); } StringBuilder strTypes = new StringBuilder(""); for (int i = 0; types != null && i < types.length; i++) { strTypes.append((i == 0 ? "" : ",") + types[i]); } StringBuilder strConvs = new StringBuilder(String.valueOf(converter)); StringBuilder strArgs = new StringBuilder(""); for (int i = 0; args != null && i < args.length; i++) { strArgs.append((i == 0 ? "" : ",") + args[i]); } StringBuilder strForm = new StringBuilder(String.valueOf(formatter)); StringBuilder strFormArgs = new StringBuilder(""); for (int i = 0; formattersArgs != null && i < formattersArgs.length; i++) { strFormArgs.append((i == 0 ? "" : ",") + formattersArgs[i]); } return index + ",'" + fieldName + "'(" + ignore + "," + eval + ")," + strNames + "( default '" + def + "'), coltype[" + coltype + "], " + strTypes + ",[" + strConvs + "],[" + strArgs + "],[" + strForm + "],[" + strFormArgs + "],(" + comparator + ")"; } } /** * Flag is an object is mapped or not. * * @param mapped * true, to be mapped, false, otherwise. */ public void setMapped(boolean mapped) { this.mapped = mapped; } /** * Says if the instance show be mapped or not. * * @return true, to map, false, otherwise. */ public boolean isMapped() { return mapped; } /** * Process a given row. * * @param context * The context. * @param table * The table. * @param row * The row. * @param result * The result set. * @return Object instance created. * @throws Exception * On processing errors. */ protected Object processLine(IContext context, TableAdapter table, RowAdapter row, IResultSet result) throws Exception { Object instance = create(context, table, row); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("CREATE:" + instance); } if (isSpecial()) { action(context, instance, row, result); result.addResult(Success.INSTANCE, context.newBlock(row.getNode(), this)); return instance; } if (populate(context, table, row, result, instance)) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("POPULATE:" + instance); } String keyBefore = makeKey(instance); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("KEYBEFORE:" + keyBefore); } Object obj = instances.get(keysBefore.get(keyBefore)); if (obj != null && isMapped()) { result.addResult(Failure.INSTANCE, context.newBlock(row.getNode(), this), new PluginException("Key '" + keyBefore + "' already used by :" + obj)); for (CellAdapter cell : row.getCells()) { String title = cell.getAttribute("title", "|"); String old = title.substring(0, title.lastIndexOf('|')); if (old.isEmpty()) { cell.removeAttribute("title"); } else { cell.setAttribute("title", old); } } } else { action(context, instance, row, result); if (isMapped()) { String keyAfter = makeKey(instance); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("KEYAFTER:" + keyAfter); } mapObject(instance, keyBefore, keyAfter); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("SAVE:" + keysBefore); UtilLog.LOG.debug("INST:" + instances); } } result.addResult(Success.INSTANCE, context.newBlock(row.getNode(), this)); } } return instance; } /** * Create the object based on a row. * * @param context * The test context. * @param table * The table. * @param row * The row. * @return The object corresponding to the row. * @throws Exception * On creation errors. */ protected Object create(IContext context, TableAdapter table, RowAdapter row) throws Exception { Object result = null; if (creator != null) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("CREATOR_ROW(" + row + ")"); } result = creatorInstance.create(context, table, row, typeInstance); } if (result == null) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("NEW_INSTANCE_ROW(" + row + ")"); } if (isSpecial()) { CellAdapter cell = row.getCell(0); if (typeInstance.isEnum()) { result = cell.getObject(context, true); } else { Constructor<?> constructor = typeInstance.getConstructor(String.class); Object tmp = cell.getObject(context, true); result = constructor.newInstance(tmp); } } else { result = typeInstance.newInstance(); } } return result; } /** * Populate the object instance with row information. * * @param instance * The object instance. * @param context * The test context. * @param header * The table header. * @param row * The row with attribute informations. * @param result * The result set. * @return The object filled with row information. * @throws Exception * On population errors. */ protected boolean populate(IContext context, TableAdapter table, RowAdapter row, IResultSet result, Object instance) throws Exception { boolean ok = true; for (int i = 0; i < fields.size(); i++) { if (row.getCellsCount() != fields.size()) { result.addResult(Failure.INSTANCE, context.newBlock(row.getNode(), this), "Number of cells(" + row.getCellsCount() + ") is different of headers(" + fields.size() + ")"); ok = false; break; } CellAdapter cell = row.getCell(i); try { Field f = fields.get(i); if (f.isIgnore()) { continue; } if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("ON>" + f.getFullName()); } String text = null; Object value = null; boolean hasConverter = f.converter != null || cell.hasAttribute(INodeHolder.ATTRIBUTE_CONVERTER); // converter prevails over any type inference if (!hasConverter && (f.isCollection() || f.isArray())) { Node node = cell.getNode(); Nodes childs = node.query("descendant::table"); if (childs.size() > 0) { Node collectionNode = childs.get(0); INodeHolder holder = SRServices.get(INodeHolderFactory.class).newHolder(collectionNode); String collectionName = holder.getAttribute("collection"); value = context.getByName(collectionName); if (value != null) { if (f.isCollection()) { String str = holder.getAttribute("coltype", f.coltype); if (str != null) { Class<?> coltype = Class.forName(str); Collection<Object> collection = (Collection<Object>) coltype.newInstance(); collection.addAll((Collection<?>) value); value = collection; } } if (f.isArray()) { List<?> tmp = (List<?>) value; Class<?> type = f.getSpecificType().getComponentType(); Object target = Array.newInstance(type, tmp.size()); value = tmp.toArray((Object[]) target); } } } } else { if (!cell.hasAttribute(INodeHolder.ATTRIBUTE_EVALUATION)) { if (!f.eval) { cell.setAttribute(INodeHolder.ATTRIBUTE_EVALUATION, String.valueOf(f.eval)); } } if (f.converter != null && !cell.hasAttribute(INodeHolder.ATTRIBUTE_CONVERTER)) { cell.setAttribute(INodeHolder.ATTRIBUTE_CONVERTER, f.converter); } for (int j = 0; f.args != null && j < f.args.length; j++) { if (!cell.hasAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_CONVERTER_PREFIX + j)) { cell.setAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_CONVERTER_PREFIX + j, f.args[j]); } } if (f.formatter != null && !cell.hasAttribute(INodeHolder.ATTRIBUTE_FORMATTER)) { cell.setAttribute(INodeHolder.ATTRIBUTE_FORMATTER, f.formatter); } for (int j = 0; f.formattersArgs != null && j < f.formattersArgs.length; j++) { if (!cell.hasAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_FORMATTER_PREFIX + j)) { cell.setAttribute(INodeHolder.ATTRIBUTE_ARGUMENT_FORMATTER_PREFIX + j, f.formattersArgs[j]); } } value = cell.getObject(context, true); text = String.valueOf(value); if (text.isEmpty()) { value = UtilExpression.evaluate(f.def, context, true); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("USING_DEFAULT>" + value); } } if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("VALUE>" + value); } } setValue(context, table, row, instance, f, value); String out = String.valueOf(value); if (text != null && !text.equals(out)) { cell.append("{" + out + "}"); } if (UtilLog.LOG.isDebugEnabled()) { Element ele = new Element("span"); ele.addAttribute(new Attribute("class", "object")); ele.appendChild("(" + (value != null ? value.getClass().getName() : "null") + ")"); cell.append(ele); } } catch (Exception e) { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug(e.getMessage(), e); } result.addResult(Failure.INSTANCE, context.newBlock(cell.getNode(), this), e); ok = false; } } if (!ok) { result.addResult(Warning.INSTANCE, context.newBlock(row.getNode(), this), "Could not create instance."); } return ok; } /** * Set the field with the given value. * * @param context * The test context. * @param table * The table. * @param row * The row corresponding to the object. * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set in instance for field 'f'. * @throws Exception * On set value errors. */ protected void setValue(IContext context, TableAdapter table, RowAdapter row, Object instance, Field f, Object value) throws Exception { // populate inner objects Object current = instance; for (int i = 0; i < f.names.length - 1; i++) { Object tmp = PropertyUtils.getProperty(current, f.names[i]); if (tmp == null) { if (creator != null) { tmp = creatorInstance.create(context, table, row, f.types[i]); } if (tmp == null) { if (f.types[i] == List.class) { tmp = new LinkedList<Object>(); } else { tmp = f.types[i].newInstance(); } } PropertyUtils.setProperty(current, f.names[i], tmp); } current = tmp; } if (value == null) { setObject(instance, f, null); } else { Class<?> st = f.getSpecificType(); if (st == boolean.class || st == Boolean.class) { setBoolean(instance, f, value); } else if (st == char.class || st == Character.class) { setChar(instance, f, value); } else if (st == short.class || st == Short.class) { setShort(instance, f, value); } else if (st == int.class || st == Integer.class) { setInteger(instance, f, value); } else if (st == long.class || st == Long.class) { setLong(instance, f, value); } else if (st == float.class || st == Float.class) { setFloat(instance, f, value); } else if (st == double.class || st == Double.class) { setDouble(instance, f, value); } else if (SRServices.getObjectManager().isBound(st)) { setEntity(instance, f, value); } else { setObject(instance, f, value); } } } /** * Sets a boolean to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setBoolean(Object instance, Field f, Object value) throws Exception { PropertyUtils.setProperty(instance, f.getFullName(), Boolean.valueOf(String.valueOf(value))); } /** * Sets a char to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setChar(Object instance, Field f, Object value) throws Exception { PropertyUtils.setProperty(instance, f.getFullName(), Character.valueOf(String.valueOf(value).charAt(0))); } /** * Sets a short to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setShort(Object instance, Field f, Object value) throws Exception { PropertyUtils.setProperty(instance, f.getFullName(), Short.valueOf(String.valueOf(value))); } /** * Sets an integer to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setInteger(Object instance, Field f, Object value) throws Exception { PropertyUtils.setProperty(instance, f.getFullName(), Integer.valueOf(String.valueOf(value))); } /** * Sets a long to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setLong(Object instance, Field f, Object value) throws Exception { PropertyUtils.setProperty(instance, f.getFullName(), Long.valueOf(String.valueOf(value))); } /** * Sets a float to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setFloat(Object instance, Field f, Object value) throws Exception { PropertyUtils.setProperty(instance, f.getFullName(), Float.valueOf(String.valueOf(value))); } /** * Sets a double to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setDouble(Object instance, Field f, Object value) throws Exception { PropertyUtils.setProperty(instance, f.getFullName(), Double.valueOf(String.valueOf(value))); } /** * If there is some plugin of the object type specified in field, use the * plugin object version. * * @param instance * The object instance. * @param f * The field metadata. * @param value * The value to be set. * @throws Exception * On set errors. */ protected void setEntity(Object instance, Field f, Object value) throws Exception { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("LOOKUP(" + f.getSpecificType() + ")"); } Object obj = SRServices.getObjectManager().get(f.getSpecificType(), String.valueOf(value)); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("FOUND(" + obj + ")"); } PropertyUtils.setProperty(instance, f.getFullName(), obj); } /** * Sets an object to a field. * * @param instance * The object instance. * @param f * The field information. * @param value * The value to be set. * @throws Exception * On setting errors. */ protected void setObject(Object instance, Field f, Object value) throws Exception { if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("OBJECT(" + f.getSpecificType() + ")"); } PropertyUtils.setProperty(instance, f.getFullName(), value); } /** * This method can be and should be overridden to perform save, comparison, * etc for updates. * * @param context * Test context. * @param instance * The object instance. * @param row * The row adapter. * @param result * The result set. * @throws Exception * On action errors. */ protected abstract void action(IContext context, Object instance, RowAdapter row, IResultSet result) throws Exception; /** * Creates the key to be used for storing/recovering object information. * * @param instance * The object instance. * @return The key corresponding to the object. * @throws Exception * On key generation errors. */ public String makeKey(Object instance) throws Exception { StringBuilder str = new StringBuilder(""); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("KEYS>" + reference); } for (int i = 0; i < references.size(); i++) { str.append((i == 0 ? "" : separator) + PropertyUtils.getProperty(instance, references.get(i))); } if (references.isEmpty()) { String key = "fake_" + System.currentTimeMillis() + "_" + System.nanoTime(); if (UtilLog.LOG.isDebugEnabled()) { UtilLog.LOG.debug("FAKE_KEY>" + key); } str.append(key); } return str.toString(); } /** * Maps the object using the keys before and after Hibernate saving. * * @param instance * The object instance. * @param keyBefore * The key before saving. * @param keyAfter * The key after saving. */ public void mapObject(Object instance, String keyBefore, String keyAfter) { keysBefore.put(keyBefore, keyAfter); instances.put(keyAfter, instance); } /** * Merge to equivalent plugins. * * @param old * Source of copy. * @return The merged plugin. */ public AbstractPluginObject merge(AbstractPluginObject old) { keysBefore.putAll(old.keysBefore); instances.putAll(old.instances); return this; } /** * Given a key, returns the corresponding object instance. * * @param key * The object key. * @return The object. * @throws PluginException * If object with the given key is not present in plugin. */ public Object getObject(String key) throws PluginException { Object result = instances.get(keysBefore.get(key)); if (result == null) { throw new PluginException("Instance '" + key + "' of '" + typeInstance.getName() + "' not found."); } return result; } /** * Set of handled objects. * * @return A collection of object under control. */ public List<Object> getObjects() { return new LinkedList<Object>(instances.values()); } /** * Remove an object from mapping. * * @param key * The key. * @return The object removed. * @throws PluginException * On lookup errors. */ public Object removeObject(String key) throws PluginException { Object old = keysBefore.get(key); keysBefore.remove(key); if (old == null) { throw new PluginException("Instance '" + key + "' of '" + typeInstance.getName() + "' not found."); } old = instances.get(old); instances.remove(old); if (old == null) { throw new PluginException("Instance '" + key + "' of '" + typeInstance.getName() + "' not found."); } return old; } }