/** * */ package org.concord.otrunk; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.Vector; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.concord.framework.otrunk.OTID; import org.concord.framework.otrunk.OTObject; import org.concord.framework.otrunk.OTObjectList; import org.concord.framework.otrunk.OTObjectMap; import org.concord.framework.otrunk.OTObjectService; import org.concord.framework.otrunk.OTResourceList; import org.concord.framework.otrunk.OTResourceMap; import org.concord.framework.otrunk.OTXMLString; import org.concord.framework.otrunk.otcore.OTClass; import org.concord.framework.otrunk.otcore.OTClassProperty; import org.concord.otrunk.datamodel.BlobResource; /** * @author scott * */ public class OTrunkUtil { private static final Logger logger = Logger.getLogger(OTrunkUtil.class.getCanonicalName()); /** * * @param propertyPath * @param root * @return * @throws NoSuchMethodException */ public static Object getPropertyValue(String propertyPath, OTObject root) throws NoSuchMethodException { Object currentObject = root; StringTokenizer toks = new StringTokenizer(propertyPath, "/"); String currentProperty = null; while(toks.hasMoreTokens()){ if(!(currentObject instanceof OTObject)){ throw new RuntimeException("Invalid path: " + propertyPath + " element: " + currentProperty + " did not return an OTObject"); } currentProperty = toks.nextToken(); currentObject = getNonPathPropertyValue(currentProperty, (OTObject) currentObject); } return currentObject; } public final static Object getNonPathPropertyValue(String propertyName, OTObject obj) throws NoSuchMethodException { // find the get or is method on the object with this name if(propertyName.endsWith("]")){ Pattern arrayPattern = Pattern.compile("(.*)\\[(\\d*)\\]"); Matcher m = arrayPattern.matcher(propertyName); if(m.matches()){ propertyName = m.group(1); String indexStr = m.group(2); OTClassProperty classProperty = obj.otClass().getProperty(propertyName); if(classProperty == null){ throw new RuntimeException("Property: " + propertyName + " doesn't exist on object: " + obj); } Object list = obj.otGet(classProperty); // remove the old one int index = Integer.parseInt(indexStr); if(list instanceof OTObjectList){ OTObjectList objList = (OTObjectList) list; if(index >= objList.size()){ return null; } return objList.get(index); } else if(list instanceof OTResourceList){ OTResourceList resList = (OTResourceList) list; return resList.get(index); } } } else { OTClassProperty classProperty = obj.otClass().getProperty(propertyName); if(classProperty == null){ throw new RuntimeException("Property: " + propertyName + " doesn't exist on object: " + obj); } return obj.otGet(classProperty); } return null; } public final static void setNonPathPropertyValue(String propertyName, OTObject obj, Object value) throws NoSuchMethodException { // check if this is an array reference if(propertyName.endsWith("]")){ if(value == null){ System.err.println("cannot store null values in a list"); return; } Pattern arrayPattern = Pattern.compile("(.*)\\[(\\d*)\\]"); Matcher m = arrayPattern.matcher(propertyName); if(m.matches()){ propertyName = m.group(1); String indexStr = m.group(2); // get the ObjectList // call set with this index OTClassProperty property = obj.otClass().getProperty(propertyName); Object list = obj.otGet(property); // remove the old one int index = Integer.parseInt(indexStr); if(list instanceof OTObjectList){ OTObjectList objList = (OTObjectList) list; if(objList.size() <= index){ while(objList.size() < index){ objList.add((OTObject)null); } objList.add((OTObject)value); } else { objList.set(index, (OTObject)value); } } else if(list instanceof OTResourceList){ OTObjectList objList = (OTObjectList) list; if(objList.size() >= (index+1)){ objList.remove(index); } objList.add(index, (OTObject)value); } } } else { OTClassProperty property = obj.otClass().getProperty(propertyName); if(property == null){ throw new NoSuchMethodException("propertyName: " + propertyName + " newValue: " + value); } Class<?> paramType = property.getType().getInstanceClass(); if(value instanceof String){ String valueStr = (String) value; if(paramType == Float.class){ value = Float.valueOf(valueStr); } else if(paramType == Integer.class){ value = Integer.valueOf(valueStr); } else if(paramType == Boolean.class){ value = Boolean.valueOf(valueStr); } else if(paramType == Double.class){ value = Double.valueOf(valueStr); } else if(paramType == Long.class){ value = Long.valueOf(valueStr); } else if(paramType == Short.class){ value = Short.valueOf(valueStr); } else if(paramType == BlobResource.class){ try { value = new URL(valueStr); } catch (MalformedURLException e) { e.printStackTrace(); } } else if(paramType == OTXMLString.class){ value = new OTXMLString(valueStr); } } if(paramType == String.class){ if(value == null){ // leave it alone so the param is set to null } else if(value.getClass().isPrimitive() || value instanceof Number || value instanceof Boolean){ value = "" + value; } else if(value instanceof OTXMLString){ value = ((OTXMLString)value).getContent(); } } obj.otSet(property, value); } } public final static String propertyToMethodCase(String property) { return property.substring(0,1).toUpperCase() + property.substring(1); } public static void setPropertyValue(String propertyPath, OTObject root, Object value) throws NoSuchMethodException { Object currentObject = root; StringTokenizer toks = new StringTokenizer(propertyPath, "/"); String currentProperty = null; while(toks.hasMoreTokens()){ if(!(currentObject instanceof OTObject)){ throw new RuntimeException("Invalid path: " + propertyPath + " element: " + currentProperty + " did not return an OTObject"); } currentProperty = toks.nextToken(); if(toks.hasMoreTokens()){ currentObject = getNonPathPropertyValue(currentProperty, (OTObject) currentObject); } else { setNonPathPropertyValue(currentProperty, (OTObject) currentObject, value); } } } /** * This method is needed if you use an object map which uses object ids for * its keys. This is useful if you want to look up one object using another * object. You cannot simply do * OTObject valueObject = map.getObject(keyObject.getGlobalId().toString()); * * because when running in user mode the keyObject id will not be the same * as what is in the map. It is wrapped with a template/user object so * changes to it can be saved correctly. * * @param map * @param keyObject * @return */ public static OTObject getObjectFromMapWithIdKeys( OTObjectMap map, OTObject keyObject) { OTObjectService objectService = keyObject.getOTObjectService(); Vector<String> keys = map.getObjectKeys(); for(int i=0; i<keys.size(); i++) { String currentKey = keys.get(i); OTID currentKeyId = objectService.getOTID(currentKey); try { OTObject currentKeyObject = objectService.getOTObject(currentKeyId); if(currentKeyObject == keyObject) { return map.getObject(currentKey); } } catch (Exception e){ e.printStackTrace(); } } return null; } public static String escapeReplacement(String replacement) { if (replacement == null) { return null; } // escape $ and \ incase these are used in the text // we need 8 backslashes here because // first java compiler strips off half so it is now // "\\\\" // then regex replacer strips off half so it is now // "\\" // and that is what we want in the replacement so the // the next replacer turns it into a "\" again. :) replacement = replacement.replaceAll("\\\\", "\\\\\\\\"); // We need 6 backslashes because // first the java compiler strips off half of them so the sting // becomes: \\\$ // then the replacer uses the backslash as a quote, and the $ // character is used to reference groups of characters, so it // must be escaped. So the 1st two are turned into one, and the // 3rd one escapes the $. So the end result is: // \$ // We need this \$ because the replacement below is going to // parse the $ otherwise replacement = replacement.replaceAll("\\$", "\\\\\\$"); return replacement; } public static void printObject(OTObject otObject) { OTClass otClass = otObject.otClass(); ArrayList<OTClassProperty> allClassProperties = otClass.getOTAllClassProperties(); for(int i=0; i<allClassProperties.size(); i++){ OTClassProperty property = allClassProperties.get(i); Object value = otObject.otGet(property); System.out.println(" " + property.getName() + "=" + value); } } /** * Compare the content of the 2 objects. This isn't complete, it will probably return * false in some cases when the objects are the same. So use with caution. * @param obj1 * @param obj2 * @return true if the same, false if different */ public static boolean compareObjects(OTObject obj1, OTObject obj2) { return compareObjects(obj1, obj2, false); } /** * Compare the content of the 2 objects. This isn't complete, it will probably return * false in some cases when the objects are the same. So use with caution. * @param obj1 * @param obj2 * @return true if the same, false if different */ public static boolean compareObjects(OTObject obj1, OTObject obj2, boolean compareXMLStrings) { // if only one is null, return false. If both are null, return true if (obj1 == null && obj2 == null){ return true; } else if (obj1 == null || obj2 == null){ return false; } OTClass otClass = obj1.otClass(); if(!otClass.equals(obj2.otClass())){ logger.fine("Object classes don't match: " + otClass + " != " + obj2.otClass()); return false; } ArrayList<OTClassProperty> allClassProperties = otClass.getOTAllClassProperties(); for(int i=0; i<allClassProperties.size(); i++){ OTClassProperty property = allClassProperties.get(i); // skip local id properties if("localId".equals(property.getName())){ continue; } boolean isSet = obj1.otIsSet(property); if(isSet != obj2.otIsSet(property)){ logFiner(property, (isSet ? "is" : "is not") + " set on obj1, but " + (isSet ? "is not" : "is") + " set on obj2"); return false; } if(!isSet){ continue; } // skip otxml strings because the will have changed and we // need special parsing code to handle them if(property.getType().getInstanceClass() == OTXMLString.class ){ if (! compareXMLStrings) { continue; } } Object value1 = obj1.otGet(property); Object value2 = obj2.otGet(property); if(value1 instanceof OTObject && value2 instanceof OTObject){ if(!compareObjects((OTObject)value1, (OTObject)value2)){ logFiner(property, "Child objects are not the same"); return false; } } else if (value1 instanceof OTResourceList){ OTResourceList list1 = (OTResourceList) value1; OTResourceList list2 = (OTResourceList) value2; if(list1.size() != list2.size()){ logFiner(property, "resource lists have different sizes -- " + list1.size() + " != " + list2.size()); return false; } for(int j=0; j<list1.size(); j++){ if(!list1.get(j).equals(list2.get(j))){ logFiner(property, "resource list item " + j + " is not the same: '" + list1.get(j) + "' != '" + list2.get(j) + "'"); return false; } } } else if (value1 instanceof OTObjectList){ OTObjectList list1 = (OTObjectList) value1; OTObjectList list2 = (OTObjectList) value2; if(list1.size() != list2.size()){ logFiner(property, "object lists have different sizes -- " + list1.size() + " != " + list2.size()); return false; } for(int j=0; j<list1.size(); j++){ if(!compareObjects(list1.get(j), list2.get(j))){ logFiner(property, "object list item " + j + " is not the same"); return false; } } } else if (value1 instanceof OTResourceMap){ OTResourceMap map1 = (OTResourceMap) value1; OTResourceMap map2 = (OTResourceMap) value2; if(map1.size() != map2.size()){ logFiner(property, "resource maps have different sizes -- " + map1.size() + " != " + map2.size()); return false; } String[] objectKeys = map1.getKeys(); for(int j=0; j<objectKeys.length; j++){ String key = objectKeys[j]; if(! map1.get(key).equals(map2.get(key))){ logFiner(property, "resource map item with key '" + key + "' is not the same: '" + map1.get(key) + "' != '" + map2.get(key) + "'"); return false; } } } else if (value1 instanceof OTObjectMap){ OTObjectMap map1 = (OTObjectMap) value1; OTObjectMap map2 = (OTObjectMap) value2; if(map1.size() != map2.size()){ logFiner(property, "object maps have different sizes -- " + map1.size() + " != " + map2.size()); return false; } Vector<String> objectKeys = map1.getObjectKeys(); for(int j=0; j<objectKeys.size(); j++){ String key = objectKeys.get(j); if(!compareObjects(map1.getObject(key), map2.getObject(key))){ logFiner(property, "object map item with key '" + key + "' is not the same"); return false; } } } else if(value1 instanceof BlobResource && value2 instanceof BlobResource){ BlobResource blob1 = (BlobResource) value1; BlobResource blob2 = (BlobResource) value2; byte[] bytes1 = blob1.getBytes(); byte[] bytes2 = blob2.getBytes(); if(bytes1.length != bytes2.length){ logFiner(property, "blobs have different byte[] lengths"); return false; } for(int j=0;j<bytes1.length;j++){ if(bytes1[j] != bytes2[j]){ logFiner(property, "byte at index " + j + " doesn't match: " + bytes1[j] + " != " + bytes2[j]); return false; } } } else if (value1 instanceof OTXMLString) { // FIXME parse for object references and be sure to compare those objects String string1 = ((OTXMLString) value1).getContent(); String string2 = ((OTXMLString) value1).getContent(); // ignore whitespace string1 = string1.replaceAll("[ ]+", " "); string2 = string2.replaceAll("[ ]+", " "); if (!string1.equals(string2)) { logFiner(property, "xmls strings don't match: '" + string1 + "' != '" + string2 + "'"); } } else { if(!value1.equals(value2)){ logFiner(property, "values don't match: '" + value1 + "' != '" + value2 + "'"); return false; } } } return true; } private static void logFiner(OTClassProperty property, String msg) { logger.finer("'" + property.getName() + "': " + msg); } public static boolean listEquals(OTObjectList list1, OTObjectList list2) { if(list1.size() != list2.size()){ return false; } for(int j=0; j<list1.size(); j++){ if(list1.get(j) == null){ if(list2.get(j) == null){ continue; } else { return false; } } if(!list1.get(j).equals(list2.get(j))){ return false; } } return true; } }