package gr.ntua.ivml.mint.mapping; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import net.sf.json.JSONSerializer; /** * * Class wraps a mapping JSONObject and handles operations on it. * */ public class JSONMappingHandler { public static final String TEMPLATE_NAMESPACES = "namespaces"; public static final String TEMPLATE_TEMPLATE = "template"; public static final String TEMPLATE_GROUPS = "groups"; public static final String ELEMENT_NAME = "name"; public static final String ELEMENT_PREFIX = "prefix"; public static final String ELEMENT_MINOCCURS = "minOccurs"; public static final String ELEMENT_MAXOCCURS = "maxOccurs"; public static final String ELEMENT_FIXED = "fixed"; public static final String ELEMENT_MANDATORY = "mandatory"; public static final String ELEMENT_LABEL = "label"; public static final String ELEMENT_CONDITION = "condition"; public static final String ELEMENT_MAPPINGS = "mappings"; public static final String ELEMENT_CHILDREN = "children"; public static final String ELEMENT_ATTRIBUTES = "attributes"; public static final String ELEMENT_ENUMERATIONS = "enumerations"; public static final String ELEMENT_REMOVABLE = "duplicate"; public static final String MAPPING_CONSTANT = "constant"; public static final String MAPPING_XPATH = "xpath"; JSONObject object = null; JSONMappingHandler(JSONObject mapping) { if(mapping == null) { throw new NullPointerException(); } else { this.object = mapping; } } public String toString() { return object.toString(); } /** * @return true if handler handles the whole mapping object */ public boolean isTopLevelMapping() { if(object.has(TEMPLATE_TEMPLATE)) { return true; } return false; } /** * @return true if handler handles an element */ public boolean isElement() { if(object.has("name")) { if(!object.getString("name").startsWith("@")) { return true; } } return false; } /** * @return true if handler handles an attribute */ public boolean isAttribute() { if(object.has("name")) { if(object.getString("name").startsWith("@")) { return true; } } return false; } /** * @param name element name of a group from configuration's "groups" array * @return JSONObject for the requested group */ public JSONObject getGroup(String name) { if(object.has(TEMPLATE_GROUPS)) { JSONArray groups = object.getJSONArray(TEMPLATE_GROUPS); Iterator i = groups.iterator(); while(i.hasNext()) { JSONObject group = (JSONObject) i.next(); if(group.has("element")) { if(group.getString("element").compareTo(name) == 0) { return group; } } } } return null; } /** * @param name group name from configuration "groups" array * @return handler for the requested group */ public JSONMappingHandler getGroupHandler(String name) { JSONObject group = this.getGroup(name); if(group != null) { JSONObject contents = group.getJSONObject("contents"); return new JSONMappingHandler(contents); } return null; } /** * Get a map of handlers for all mapping's groups. * Key is the group's element. * Must be called on top level mapping handler. * @return */ public Map<String, JSONMappingHandler> getGroupHandlers() { HashMap<String, JSONMappingHandler> map = new HashMap<String, JSONMappingHandler>(); if(this.object.has(TEMPLATE_GROUPS)) { JSONArray groups = this.object.getJSONArray(TEMPLATE_GROUPS); for(int i = 0; i < groups.size(); i ++) { JSONObject group = groups.getJSONObject(i); JSONMappingHandler handler = new JSONMappingHandler(group); map.put(group.getString("element"), handler); } } return map; } /** * @return JSONArray of handler's attributes */ public JSONArray getAttributes() { if(object.has(ELEMENT_ATTRIBUTES)) { return object.getJSONArray(ELEMENT_ATTRIBUTES); } return null; } /** * Get a handler for an attribute. * @param attribute name (optionally starting with @) * @return the attribute handler or null if no attribute was found. */ public JSONMappingHandler getAttribute(String attribute) { if(attribute.startsWith("@")) attribute = attribute.substring(1); String name = attribute; String prefix = ""; if(attribute.contains(":")) { String[] tokens = attribute.split(":"); if(tokens.length > 2) return null; else if(tokens.length > 1) { prefix = tokens[0]; name = tokens[1]; } } JSONArray attributes = this.getAttributes(); if(attributes != null) { for(Object o: attributes) { JSONObject a = (JSONObject) o; if(a.has(ELEMENT_NAME) && a.getString(ELEMENT_NAME).equals("@" + name)) { if(a.has(ELEMENT_PREFIX) && a.getString(ELEMENT_PREFIX).equals(prefix)) { return new JSONMappingHandler(a); } } } } return null; } /** * Get a handler for a child. * @param child name * @return the child handler or null if no child was found. */ public JSONMappingHandler getChild(String child) { String name = child; String prefix = ""; if(child.contains(":")) { String[] tokens = child.split(":"); if(tokens.length > 2) return null; else if(tokens.length > 1) { prefix = tokens[0]; name = tokens[1]; } } JSONArray children = this.getChildren(); if(children != null) { for(Object o: children) { JSONObject c = (JSONObject) o; if(c.has(ELEMENT_NAME) && c.getString(ELEMENT_NAME).equals(name)) { if(c.has(ELEMENT_PREFIX) && c.getString(ELEMENT_PREFIX).equals(prefix)) { return new JSONMappingHandler(c); } } } } return null; } /** * @return JSONArray of handler's children */ public JSONArray getChildren() { if(object.has(ELEMENT_CHILDREN)) { return object.getJSONArray(ELEMENT_CHILDREN); } return null; } /** * @return JSONArray of handler's mappings */ public JSONArray getMappings() { if(object.has(ELEMENT_MAPPINGS)) { return object.getJSONArray(ELEMENT_MAPPINGS); } return null; } /** * Get a JSONArray of strings representing the handler's enumerations. * @return JSONArray of handler's enumerations or null if no enumerations exist. */ public JSONArray getEnumerations() { if(object.has(ELEMENT_ENUMERATIONS)) { return object.getJSONArray(ELEMENT_ENUMERATIONS); } return null; } /** * Removes all handler's enumerations. */ public void removeEnumerations() { if(object.has(ELEMENT_ENUMERATIONS)) { object.remove(ELEMENT_ENUMERATIONS); } } /** * Adds an enumeration. * * @param enumeration the enumeration to be added. */ public void addEnumeration(String enumeration) { if(!object.has(ELEMENT_ENUMERATIONS)) { object.element(ELEMENT_ENUMERATIONS, new JSONArray()); } JSONArray enumerations = this.getEnumerations(); enumerations.add(enumeration); } public void setString(String key, String value) { object.element(key, value); } public void setObject(String key, JSONObject value) { object.element(key, value); } public void setArray(String key, JSONArray value) { object.element(key, value); } public String getString(String key) { if(object.has(key)) { return object.getString(key); } return null; } public String getOptString(String key) { if(object.has(key)) { return object.getString(key); } return ""; } public JSONObject getObject(String key) { if(object.has(key)) { return object.getJSONObject(key); } return null; } /** * @param key mapping key * @return handler for requested key */ public JSONMappingHandler getHandler(String key) { if(object.has(key)) { return new JSONMappingHandler(object.getJSONObject(key)); } return null; } public JSONArray getArray(String key) { if(object.has(key)) { return object.getJSONArray(key); } return null; } public void addMapping(String type, String value) { this.getMappings().add(new JSONObject().element("type", type).element("value", value)); } /** * Adds a constant mapping with specified value * @param value constant value */ public void addConstantMapping(String value) { this.addMapping(MAPPING_CONSTANT, value); } /** * Adds an xpath mapping with specified value * @param xpath the xpath mapping */ public void addXPathMapping(String xpath) { this.addMapping(MAPPING_XPATH, xpath); } /** * @return true if mapping is fixed inside the mapping editor. */ public boolean isFixed() { return object.has(ELEMENT_FIXED); } /** * Sets the fixed property of a mapping. Fixed mappings cannot change using the mapping editor. * @param f fixed property */ public void setFixed(boolean f) { if(f) { object.element(ELEMENT_FIXED, ""); } else { object.remove(ELEMENT_FIXED); } } /** * @return true if mapping is forced as mandatory. */ public boolean isMandatory() { return object.has(ELEMENT_MANDATORY); } /** * Sets the mandatory property of a mapping forcing the mapping editor to consider it as mandatory. * @param m mandatory property */ public void setMandatory(boolean m) { if(m) { object.element(ELEMENT_MANDATORY, ""); } else { object.remove(ELEMENT_MANDATORY); } } /** * True if element can be removed from the mapping editor. * @return true if element can be removed from the mapping editor. */ public boolean isRemovable() { return this.object.has(ELEMENT_REMOVABLE); } /** * Set removable state of this element. Removable elements can be removed by the user using the mapping editor. * @param r removable state. */ public void setRemovable(boolean r) { if(r) { if(!this.isRemovable()) { this.object.element(ELEMENT_REMOVABLE, ""); } } else { if(this.isRemovable()) { this.object.remove(ELEMENT_REMOVABLE); } } } /** * True if handler is repeatable (ie. maxOccurs == unbounded). * @return true if handler is repeatable, false otherwise. */ public boolean isRepeatable() { if(object.has(ELEMENT_MAXOCCURS)) { int maxOccurs = Integer.parseInt(object.getString(ELEMENT_MAXOCCURS)); if(!this.isAttribute() && maxOccurs < 0) return true; } return false; } /** * Gets a custom label set for this element. * @return the custom label or null if none is set. */ public String getLabel() { if(object.has(ELEMENT_LABEL)) return object.getString(ELEMENT_LABEL); return null; } /** * Sets a custom label for this element. Set to null to remove the custom label. * @param label the custom label or null if the label is to be removed. */ public void setLabel(String label) { if(label == null) { if(object.has(ELEMENT_LABEL)) object.remove(ELEMENT_LABEL); } else { object.element(ELEMENT_LABEL, label); } } /** * Gets a mapping handler for requested path. * Path is relative to the mapping handler. If mapping handler is top level handler then searches * are performed inside each group. * Use if only one instance of this path exists or if you want the first. * * @param path the requested path * @return the handler or null if not found */ public JSONMappingHandler getHandlerForPath(String path) { ArrayList<JSONMappingHandler> handlers = this.getHandlersForPath(path); if(handlers.size() > 0) return handlers.get(0); return null; } /** * Gets a list of mapping handlers for requested path. * Path is relative to the mapping handler. If mapping handler is top level handler then searches * are performed inside each group. * * @param path the requested path * @return the list of handlers found */ public ArrayList<JSONMappingHandler> getHandlersForPath(String path) { if(this.isTopLevelMapping()) { if(path.startsWith("/")) { path = path.replaceFirst("/", ""); } String[] tokens = path.split("/", 2); if(tokens.length > 0) { JSONObject group = this.getGroup(tokens[0]); if(group != null) { JSONObject contents = group.getJSONObject("contents"); return JSONMappingHandler.getHandlersForPath(contents, path); } } } else { return JSONMappingHandler.getHandlersForPath(object, path); } return new ArrayList<JSONMappingHandler>(); } private static ArrayList<JSONMappingHandler> getHandlersForPath(JSONObject object, String path) { ArrayList<JSONMappingHandler> result = new ArrayList<JSONMappingHandler>(); if(path.startsWith("/")) { path = path.replaceFirst("/", ""); } String[] tokens = path.split("/", 2); if(tokens.length > 0) { if(object.has("name")) { if(tokens[0].equals(object.getString("name"))) { if(tokens.length == 1) { result.add(new JSONMappingHandler(object)); } else { String tail = tokens[1]; if(tail.startsWith("@")) { if(object.has("attributes")) { return JSONMappingHandler.getHandlersForPath(object.getJSONArray("attributes"), tail); } } else { if(object.has("children")) { return JSONMappingHandler.getHandlersForPath(object.getJSONArray("children"), tail); } } } } } } return result; } private static ArrayList<JSONMappingHandler> getHandlersForPath(JSONArray array, String path) { ArrayList<JSONMappingHandler> result = new ArrayList<JSONMappingHandler>(); Iterator i = array.iterator(); while(i.hasNext()) { JSONObject o = (JSONObject) i.next(); result.addAll(JSONMappingHandler.getHandlersForPath(o, path)); } return result; } /** * Gets a list of mapping handlers for requested element/attribute name. * Searches are relative to the handler and return all requested elements/attributes regardless of path. * * @param name the requested element/attribute name. Attribute names should begin with '@'. * @return the list of handlers found */ public ArrayList<JSONMappingHandler> getHandlersForName(String name) { ArrayList<JSONMappingHandler> result = new ArrayList<JSONMappingHandler>(); if(this.isTopLevelMapping()) { JSONArray groups = object.getJSONArray(TEMPLATE_GROUPS); Iterator i = groups.iterator(); while(i.hasNext()) { JSONObject group = (JSONObject) i.next(); JSONObject contents = group.getJSONObject("contents"); result.addAll(JSONMappingHandler.getHandlersForName(contents, name)); } } else { if(this.getOptString("name").compareTo(name) == 0) { result.add(this); } result.addAll(JSONMappingHandler.getHandlersForName(this.getAttributes(), name)); result.addAll(JSONMappingHandler.getHandlersForName(this.getChildren(), name)); } return result; } private static ArrayList<JSONMappingHandler> getHandlersForName(JSONObject object, String name) { return new JSONMappingHandler(object).getHandlersForName(name); } private static ArrayList<JSONMappingHandler> getHandlersForName(JSONArray array, String name) { ArrayList<JSONMappingHandler> result = new ArrayList<JSONMappingHandler>(); if(array != null) { Iterator i = array.iterator(); while(i.hasNext()) { JSONObject o = (JSONObject) i.next(); result.addAll(JSONMappingHandler.getHandlersForName(o, name)); } } return result; } /** * Duplicates an element for the given path. Duplicate element is placed after original element to preserve element order. * @param path the path of the element to be duplicated, relative to the handler * @return handler for the created element or null if path was not found */ public JSONMappingHandler duplicatePath(String path) { if(!path.startsWith("/")) path = "/" + path; String[] parts = path.split("/"); StringBuffer buffer = new StringBuffer(); // if path is not a child of this handler delegate duplication to the appropriate child if(parts.length > 3) { for(int i = 1; i < parts.length - 1; i++) { buffer.append("/" + parts[i]); } JSONMappingHandler child = this.getHandlerForPath(buffer.toString()); return child.duplicatePath("/" + parts[parts.length - 2] + "/" + parts[parts.length - 1]); // else duplicate child, add to children and return } else { JSONMappingHandler original = this.getHandlerForPath(path); if(!original.isAttribute()) { JSONObject duplicate = (JSONObject) JSONSerializer.toJSON(original.toString()); duplicate.element("__duplicate", ""); int originalIndex = -1; JSONArray children = this.getChildren(); for(int i = 0; i < children.size(); i++) { JSONObject c = (JSONObject) children.get(i); if(c.has(ELEMENT_NAME) && original.has(ELEMENT_NAME) && c.getString(ELEMENT_NAME).equals(original.getString(ELEMENT_NAME))) { if(c.has(ELEMENT_PREFIX) && original.has(ELEMENT_PREFIX) && c.getString(ELEMENT_PREFIX).equals(original.getString(ELEMENT_PREFIX))) { originalIndex = i; } } } this.getChildren().add(originalIndex, duplicate); for(Object o: children) { JSONObject c = (JSONObject) o; if(c.has("__duplicate")) { c.remove("__duplicate"); return new JSONMappingHandler(c); } } } } return null; } /** * Get the namespaces JSONObject. * Keys of this object are the namespaces prefixes and values are the namespace URLs. * * @return the namespaces JSONObject or null if it does not exist; */ public JSONObject getNamespaces() { if(this.object.has(TEMPLATE_NAMESPACES)) { return this.object.getJSONObject(TEMPLATE_NAMESPACES); } return null; } /** * Get handler for the template group. * * @return handler for the template group or null if it does not exist. */ public JSONMappingHandler getTemplate() { if(this.object.has(TEMPLATE_TEMPLATE)) { return new JSONMappingHandler(this.object.getJSONObject(TEMPLATE_TEMPLATE)); } return null; } /** * Check if a key exists in the handler. * @param string key name. * @return true if key exists. */ public boolean has(String string) { return this.object.has(string); } /** * Get handler's mapping condition object * @return the condition JSONObject or null if it does not exist. */ public JSONObject getCondition() { if(this.object.has(ELEMENT_CONDITION)) { return this.object.getJSONObject(ELEMENT_CONDITION); } return null; } /** * Generic method that sets a handler's mapping condition. Condition is removed if null value is passed. * @param condition the condition JSONObject */ public void setCondition(JSONObject condition) { if(condition != null) { this.object.element(ELEMENT_CONDITION, condition); } else { this.object.remove(ELEMENT_CONDITION); } } /** * Get handler's element full name. Full name contains element's name with the element's prefix * if exists. Attributes also start with '@'. * @return element full name. */ public String getFullName() { String name = null; String prefix = null; if(this.has(ELEMENT_NAME)) name = this.getString(ELEMENT_NAME).replace("@", ""); if(this.has(ELEMENT_PREFIX)) prefix = this.getString(ELEMENT_PREFIX); String label = ((this.isAttribute())?"@":"") + ((prefix != null)?prefix+":":"") + name; return label; } public boolean hasMappingsRecursive() { if(this.has(ELEMENT_MAPPINGS) && this.getArray(ELEMENT_MAPPINGS).size() > 0) return true; else { if(this.has(ELEMENT_CHILDREN)) { JSONArray children = this.getChildren(); for(int i = 0; i < children.size(); i++) { if(new JSONMappingHandler(children.getJSONObject(i)).hasMappingsRecursive()) return true; } } if(this.has(ELEMENT_ATTRIBUTES)) { JSONArray attributes = this.getAttributes(); for(int i = 0; i < attributes.size(); i++) { if(new JSONMappingHandler(attributes.getJSONObject(i)).hasMappingsRecursive()) return true; } } } return false; } }