/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file. */
package er.extensions.foundation;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eoaccess.EOUtilities;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSSet;
import com.webobjects.foundation.NSTimestamp;
import er.extensions.components.ERXStatelessComponent;
import er.extensions.eof.ERXConstant;
import er.extensions.eof.ERXEOAccessUtilities;
import er.extensions.eof.ERXEnterpriseObject;
import er.extensions.eof.ERXReplicableInterface;
/**
* Diverse collection of utility methods for handling everything from
* EOF to foundation. In the future this class will most likely be
* split into more meaning full groups of utility methods.
*/
public class ERXUtilities {
private static final Logger log = LoggerFactory.getLogger(ERXUtilities.class);
/**
* Utility method for returning all of the primary keys for
* all of the objects that are marked for deletion in
* the editing context.
* @param ec editing context
* @return an array containing all of the dictionaries of
* primary keys for all of the objects marked for
* deletion
*/
// CHECKME: I don't think this is a value add
public static NSArray deletedObjectsPKeys(EOEditingContext ec) {
NSMutableArray result = new NSMutableArray();
for (Enumeration e = ec.deletedObjects().objectEnumerator(); e.hasMoreElements();) {
EOEnterpriseObject eo=(EOEnterpriseObject)e.nextElement();
if (eo instanceof ERXEnterpriseObject)
result.addObject(((ERXEnterpriseObject)eo).primaryKeyInTransaction());
else
result.addObject(EOUtilities.primaryKeyForObject(ec, eo));
}
return result;
}
/**
* Traverses a key path to return the last {@link EORelationship}
* object.
* <p>
* Note: that this method uses the object and not the model to traverse
* the key path, this has the added benefit of handling EOF inheritance
* @param object enterprise object to find the relationship off of
* @param keyPath key path used to find the relationship
* @return relationship object corresponding to the last property key of
* the key path.
*/
public static EORelationship relationshipWithObjectAndKeyPath(EOEnterpriseObject object, String keyPath) {
EOEnterpriseObject lastEO = relationshipObjectWithObjectAndKeyPath(object, keyPath);
EORelationship relationship = null;
if (lastEO!=null) {
EOEntity entity=ERXEOAccessUtilities.entityNamed(object.editingContext(), lastEO.entityName());
String lastKey=ERXStringUtilities.lastPropertyKeyInKeyPath(keyPath);
relationship=entity.relationshipNamed(lastKey);
}
return relationship;
}
public static NSDictionary relationshipEntityWithEntityAndKeyPath(EOEntity srcentity, String keyPath) {
//keyPath is something like 'project.user.person.firstname'
//we will get the Person entity
if (keyPath.indexOf(".") == -1) {
NSDictionary d = new NSDictionary(new Object[]{srcentity, keyPath}, new Object[]{"entity", "keyPath"});
return d;
}
while (keyPath.indexOf(".") != -1) {
String key = ERXStringUtilities.firstPropertyKeyInKeyPath(keyPath);
EORelationship rel = srcentity.relationshipNamed(key);
if (rel == null) {
break;
}
srcentity = rel.destinationEntity();
keyPath = ERXStringUtilities.keyPathWithoutFirstProperty(keyPath);
}
NSDictionary d = new NSDictionary(new Object[]{srcentity, keyPath}, new Object[]{"entity", "keyPath"});
return d;
}
public static EOEnterpriseObject relationshipObjectWithObjectAndKeyPath(EOEnterpriseObject object, String keyPath) {
if(object == null) {
return null;
}
EOEnterpriseObject lastEO=object;
if (keyPath.indexOf(".")!=-1) {
String partialKeyPath=ERXStringUtilities.keyPathWithoutLastProperty(keyPath);
Object rawLastEO=object.valueForKeyPath(partialKeyPath);
lastEO=rawLastEO instanceof EOEnterpriseObject ? (EOEnterpriseObject)rawLastEO : null;
}
return lastEO;
}
/**
* Simple utility method for deleting an array
* of objects from an editing context.
* @param ec editing context to have objects deleted from
* @param objects objects to be deleted.
*/
public static void deleteObjects(EOEditingContext ec, NSArray objects) {
if (ec == null)
throw new RuntimeException("Attempting to delete objects with a null editing context!");
if (objects != null && objects.count() > 0) {
for (Enumeration e = objects.objectEnumerator(); e.hasMoreElements();)
ec.deleteObject((EOEnterpriseObject)e.nextElement());
}
}
/**
* Utility method to get all of the framework names that
* have been loaded into the application.
* @return array containing all of the framework names
*/
public static NSArray allFrameworkNames() {
NSMutableArray frameworkNames = new NSMutableArray();
for (Enumeration e = NSBundle.frameworkBundles().objectEnumerator(); e.hasMoreElements();) {
NSBundle bundle = (NSBundle)e.nextElement();
if (bundle.name() != null)
frameworkNames.addObject(bundle.name());
else
log.warn("Null framework name for bundle: {}", bundle);
}
return frameworkNames;
}
/**
* Simple utility method for getting all of the
* entities for all of the models of a given
* model group.
* @param group eo model group
* @return array of all entities for a given model group.
*/
public static NSArray entitiesForModelGroup(EOModelGroup group) {
return ERXArrayUtilities.flatten((NSArray)group.models().valueForKey("entities"));
}
/** entity name cache */
private static NSDictionary<String, EOEntity> _entityNameEntityCache;
/**
* Finds an entity given a case insensitive search
* of all the entity names.
* <p>
* Note: The current implementation caches the entity-entity name
* pair in an insensitive manner. This means that all of the
* models should be loaded before this method is called.
*/
// FIXME: Should add an EOEditingContext parameter to get the right
// EOModelGroup. Should also have a way to clear the cache.
// CHECKME: Should this even be cached?
public static EOEntity caseInsensitiveEntityNamed(String entityName) {
EOEntity entity = null;
if (entityName != null) {
if (_entityNameEntityCache == null) {
NSMutableDictionary<String, EOEntity>entityNameDict = new NSMutableDictionary<>();
for (Enumeration<EOEntity> e = entitiesForModelGroup(ERXEOAccessUtilities.modelGroup(null)).objectEnumerator(); e.hasMoreElements();) {
EOEntity anEntity = e.nextElement();
entityNameDict.setObjectForKey(anEntity, anEntity.name().toLowerCase());
}
_entityNameEntityCache = entityNameDict;
}
entity = _entityNameEntityCache.objectForKey(entityName.toLowerCase());
}
return entity;
}
/**
* Generates a string representation of the current stacktrace.
*
* @return current stacktrace.
*/
public static String stackTrace() {
String result = null;
try {
throw new Throwable();
} catch (Throwable t) {
result = ERXUtilities.stackTrace(t);
}
String separator = System.getProperties().getProperty("line.separator");
// Chop off the 1st line, "java.lang.Throwable"
//
int offset = result.indexOf(separator);
result = result.substring(offset+1);
// Chop off the lines at the start that refer to ERXUtilities
//
offset = result.indexOf(separator);
while (result.substring(0,offset).indexOf("ERXUtilities.java") >= 0) {
result = result.substring(offset+1);
offset = result.indexOf(separator);
}
return separator+result;
}
/**
* Converts a throwable's stacktrace into a
* string representation.
* @param t throwable to print to a string
* @return string representation of stacktrace
*/
public static String stackTrace(Throwable t) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
PrintStream printStream = new PrintStream(baos);
t.printStackTrace(printStream);
return baos.toString();
}
/**
* Useful interface for binding objects to
* WOComponent bindings where you want to
* delay the evaluation of the boolean operation
* until <code>valueForBinding</code> is
* actually called. See {@link ERXStatelessComponent}
* for examples.
*/
public static interface BooleanOperation {
public boolean value();
}
/**
* Useful interface for binding objects to
* WOComponent bindings where you want to
* delay the evaluation of the operation
* until <code>valueForBinding</code> is
* actually called. See {@link ERXStatelessComponent}
* for examples.
*/
public static interface Operation {
public Object value();
}
/**
* Generic callback interface with a context
* object.
*/
public static interface Callback {
public Object invoke(Object ctx);
}
/**
* Generic boolean callback interface with a
* context object.
*/
public static interface BooleanCallback {
public boolean invoke(Object ctx);
}
/**
* Gets rid of all ' from a String.
* @param aString string to check
* @return string without '
*/
// CHECKME: Is this a value add? I don't think so.
public static String escapeApostrophe(String aString) {
NSArray parts = NSArray.componentsSeparatedByString(aString,"'");
return parts.componentsJoinedByString("");
}
/** Copies values from one EO to another using an array of Attributes */
public static void replicateDataFromEOToEO(EOEnterpriseObject r1, EOEnterpriseObject r2, NSArray attributeNames){
for(Enumeration e = attributeNames.objectEnumerator(); e.hasMoreElements();){
String attributeName = (String)e.nextElement();
r2.takeValueForKey(r1.valueForKey(attributeName), attributeName);
}
}
/** Copies a relationship from one EO to another using the name of the relationship */
public static void replicateRelationshipFromEOToEO(EOEnterpriseObject r1, EOEnterpriseObject r2, String relationshipName){
for(Enumeration e = ((NSArray)r1.valueForKey(relationshipName)).objectEnumerator(); e.hasMoreElements();){
ERXReplicableInterface replicableTarget = (ERXReplicableInterface)e.nextElement();
r2.addObjectToBothSidesOfRelationshipWithKey(replicableTarget.replicate(r2.editingContext()), relationshipName);
}
}
/** Copies a relationship from one EO to another using the name of the relationship */
public static void deplicateRelationshipFromEO(EOEnterpriseObject r1, String relationshipName){
//System.out.println("r1 "+r1);
//System.out.println("relationshipName "+relationshipName);
//System.out.println("array "+r1.valueForKey(relationshipName));
for(Enumeration e = ((NSArray)r1.valueForKey(relationshipName)).objectEnumerator(); e.hasMoreElements();){
ERXReplicableInterface replicableTarget = (ERXReplicableInterface)e.nextElement();
//System.out.println("replicableTarget "+replicableTarget);
r1.removeObjectFromBothSidesOfRelationshipWithKey((EOEnterpriseObject)replicableTarget,
relationshipName);
replicableTarget.deplicate();
}
}
/**
* Returns a deep clone of the given object. A deep clone will attempt
* to clone any contained values (in the case of an NSArray or NSDictionary)
* as well as the value itself.
*
* @param obj the object to clone
* @param onlyCollections if true, only collections will be cloned, not individual values
* @return a deep clone of obj
*/
@SuppressWarnings("unchecked")
public static <T> T deepClone(T obj, boolean onlyCollections) {
Object clone;
if (obj instanceof NSArray) {
clone = ERXArrayUtilities.deepClone((NSArray)obj, onlyCollections);
}
else if (obj instanceof NSSet) {
clone = ERXArrayUtilities.deepClone((NSSet)obj, onlyCollections);
}
else if (obj instanceof NSDictionary) {
clone = ERXDictionaryUtilities.deepClone((NSDictionary)obj, onlyCollections);
}
else if (!onlyCollections && obj instanceof Cloneable) {
try {
Method m = obj.getClass().getMethod("clone", ERXConstant.EmptyClassArray);
clone = m.invoke(obj, ERXConstant.EmptyObjectArray);
}
catch (Exception e) {
throw new RuntimeException("Failed to clone " + obj + ".", e);
}
}
else {
clone = obj;
}
return (T)clone;
}
}