/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted
* software. For permitted uses, licensing options and redistribution, please see
* the LicensingInformation file at the root level of the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.util;
import com.googlecode.openbeans.BeanInfo;
import com.googlecode.openbeans.Introspector;
import com.googlecode.openbeans.PropertyDescriptor;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hypergraphdb.HGConfiguration;
import org.hypergraphdb.HGEnvironment;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGHandle;
import org.hypergraphdb.HGLink;
import org.hypergraphdb.HGPersistentHandle;
import org.hypergraphdb.HGQuery;
import org.hypergraphdb.HGRandomAccessResult;
import org.hypergraphdb.HGSearchResult;
import org.hypergraphdb.HyperGraph;
import org.hypergraphdb.HGRandomAccessResult.GotoResult;
/**
*
* <p>
* The mandatory bag of static utility method class.
* </p>
*
* @author Borislav Iordanov
*
*/
public class HGUtils
{
public static final HGPersistentHandle [] EMPTY_HANDLE_ARRAY = new HGPersistentHandle[0];
public static boolean isEmpty(String s)
{
return s == null || s.length() == 0;
}
public static void throwRuntimeException(Throwable t)
{
if (t instanceof RuntimeException)
throw (RuntimeException)t;
else if (t instanceof Error)
throw (Error)t;
else
throw new HGException(t);
}
public static Throwable getRootCause(Throwable t)
{
if (t != null)
while (t.getCause() != null)
t = t.getCause();
return t;
}
/**
* <p>
* Compare two objects for equality, checking for <code>null</code> values as well.
* </p>
*/
public static boolean eq(Object left, Object right)
{
if (left == right)
return true;
else if (left == null)
return false;
else if (right == null)
return false;
else
return left.equals(right);
}
/**
* <p>Compare two arrays for equality. This will perform a deep comparison, return
* <code>true</code> if and only if all elements of the passed in arrays are equal.</p>
*/
public static boolean eq(Object [] left, Object [] right)
{
if (left == right)
return true;
else if (left == null || right == null)
return false;
else if (left.length != right.length)
return false;
for (int i = 0; i < left.length; i++)
if (!eq(left[i], right[i]))
return false;
return true;
}
public static boolean eq(byte [] left, byte [] right)
{
if (left == right)
return true;
else if (left == null || right == null || left.length != right.length)
return false;
for (int i = 0; i < left.length; i++)
if (left[i] != right[i])
return false;
return true;
}
public static boolean eq(Ref<?> r1, Ref<?> r2)
{
if (r1 == null) return r2 == null;
else if (r2 == null) return false;
else return HGUtils.eq(r1.get(), r2.get());
}
public static boolean eq(Ref<?> [] left, Ref<?> [] right)
{
if (left == right)
return true;
else if (left == null || right == null)
return false;
else if (left.length != right.length)
return false;
for (int i = 0; i < left.length; i++)
if (!eq(left[i], right[i]))
return false;
return true;
}
/**
* <p>Return an object's hash code or 0 if the object is <code>null</code>.</p>
*/
public static int hashIt(Object o)
{
return HashCodeUtil.hash(0, o);
}
/**
* <p>Return a composite hash code of two objects.</p>
*/
public static int hashThem(Object one, Object two)
{
return hashIt(one) | hashIt(two);
}
/**
* <p>
* Print the full stack trace of a <code>Throwable</code> object into a
* string buffer and return the corresponding string.
* </p>
*/
public static String printStackTrace(Throwable t)
{
java.io.StringWriter strWriter = new java.io.StringWriter();
java.io.PrintWriter prWriter = new java.io.PrintWriter(strWriter);
t.printStackTrace(prWriter);
prWriter.flush();
return strWriter.toString();
}
public static void printStackTrace(StackTraceElement [] trace, PrintStream out)
{
for (StackTraceElement el : trace)
out.println(el.toString());
}
public static void logCallStack(PrintStream out)
{
printStackTrace(Thread.currentThread().getStackTrace(), out);
}
public static void closeNoException(HGSearchResult<?> rs)
{
if (rs == null) return;
try { rs.close(); } catch (Throwable t) { }
}
public static void wrapAndRethrow(Throwable t)
{
if (t instanceof RuntimeException)
throw (RuntimeException)t;
else if (t instanceof Error)
throw (Error)t;
else
throw new HGException(t);
}
/**
* <p>
* Load a class using the class loader configured for the passed in {@link HyperGraph} instance, if
* such a loader was configured. If no class loader specific to the DB instance was configured, try
* the thread context class loader. Finally, fall back to <code>HGUtils.class.getClassLoader()</code>.
* </p>
*
* <p>
* If you have a DB configured class loader that you need to override with a thread context
* class loader, you'd need to implement the delegating yourself.
* </p>
*
* @param <T>
* @param graph
* @param classname
* @return
* @throws ClassNotFoundException
*/
public static <T> Class<T> loadClass(HyperGraph graph, String classname) throws ClassNotFoundException
{
return loadClass(graph.getConfig(), classname);
}
/**
* <p>
* Load a class using the class loader configured in the {@link HGConfiguration} object, if
* such a loader was configured. If there is no class loader in the configuration, try
* the thread context class loader. Finally, fall back to <code>HGUtils.class.getClassLoader()</code>.
* </p>
*
* <p>
* If you have a DB configured class loader that you need to override with a thread context
* class loader, you'd need to implement the delegating yourself.
* </p>
*
* @param <T>
* @param graph
* @param classname
* @return
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> loadClass(HGConfiguration config, String classname) throws ClassNotFoundException
{
ClassLoader loader = getClassLoader(config);
if(classname.startsWith("[L"))
return (Class<T>)Array.newInstance(loader.loadClass(
classname.substring(2, classname.length() - 1)), 0).getClass();
else if (classname.startsWith("["))
return (Class<T>)Class.forName(classname);
else
return (Class<T>)loader.loadClass(classname);
}
/**
* Returns the configured, the context or the class's classloader.
*
* @param graph
* @return never null.
*/
public static ClassLoader getClassLoader(HyperGraph graph)
{
return getClassLoader(graph.getConfig());
}
/**
* Returns the configured, the context or the class's classloader.
*
* @param config
* @return never null.
*/
public static ClassLoader getClassLoader(HGConfiguration config)
{
ClassLoader loader = config.getClassLoader();
if (loader == null)
loader = Thread.currentThread().getContextClassLoader();
if (loader == null)
loader = HGUtils.class.getClassLoader();
return loader;
}
public static HGHandle [] toHandleArray(HGLink link)
{
if (link == null)
return null;
HGHandle [] A = new HGHandle[link.getArity()];
for (int i = 0; i < link.getArity(); i++)
A[i] = link.getTargetAt(i);
return A;
}
/**
*
* <p>
* Process a potentially large query result in batches where each batch is a encapsulated
* in a single transaction. It is assumed that the result of the <code>query</code>
* is a <code>HGRandomAccessResult</code> so it is possible to quickly position at
* the beginning of the next batch.
* </p>
*
* <p>
* The <code>startAfter</code> and <code>first</code> parameters define a starting point
* for the process. Use either one of them, but not both.
* </p>
*
* @param <T>
* @param query The query that produces the result set to be scanned.
* @param F The function to perform on that query. The function takes a query result item
* and return a boolean which indicates whether to continue processing (if <code>true</code>)
* or stop (if <code>false</code>).
* @param batchSize The number of result items to encapsulate in a single transaction.
* @param startAfter Start processing at the first element after this parameter.
* @param first A 1-based index of the first element to process.
* @return The total number of elements processed.
*/
public static <T> long queryBatchProcess(HGQuery<T> query,
Mapping<T, Boolean> F,
int batchSize,
T startAfter,
long first)
{
HGSearchResult<T> rs = null;
T lastProcessed = startAfter;
long totalProcessed = 0;
while (true)
{
T txLastProcessed = lastProcessed;
int currentProcessed = 0;
query.getHyperGraph().getTransactionManager().beginTransaction();
try
{
rs = query.execute();
if (txLastProcessed == null) // if we are just starting
{
for (long i = first; i > 0; i--) // skip first 'first' entries
{
if (!rs.hasNext())
{
rs.close();
rs = null;
query.getHyperGraph().getTransactionManager().endTransaction(false);
return totalProcessed;
}
else
rs.next();
}
}
else // else, position to the end of the last successful batch
{
GotoResult gt = null;
if (rs instanceof HGRandomAccessResult)
{
HGRandomAccessResult<T> rars = (HGRandomAccessResult<T>)rs;
gt = rars.goTo(txLastProcessed, false);
}
else
{
rs.close();
rs = null;
throw new HGException("Batch processing starting at a specific element is only supported for HGRandomAccessResult.");
}
if (gt == GotoResult.nothing) // last processed was actually last element in result set
{
rs.close();
rs = null;
query.getHyperGraph().getTransactionManager().endTransaction(false);
return totalProcessed;
}
else if (gt == GotoResult.found)
{
if (!rs.hasNext())
{
rs.close();
rs = null;
query.getHyperGraph().getTransactionManager().endTransaction(false);
return totalProcessed;
}
else
rs.next();
} // else we are already positioned after the last processed, which is not present for god know why?
}
// double start = System.currentTimeMillis();
if (! (rs instanceof HGRandomAccessResult))
batchSize = Integer.MAX_VALUE;
int i;
for (i = 0; i < batchSize; i++)
{
T x = rs.current();
if (!F.eval(x))
{
rs.close();
rs = null;
query.getHyperGraph().getTransactionManager().endTransaction(true);
return totalProcessed + currentProcessed;
}
txLastProcessed = x;
currentProcessed++;
if (!rs.hasNext())
break;
else
rs.next();
}
rs.close();
rs = null;
query.getHyperGraph().getTransactionManager().endTransaction(true);
lastProcessed = txLastProcessed;
totalProcessed += currentProcessed;
// double end = System.currentTimeMillis();
if (i < batchSize)
return totalProcessed;
// System.out.println("Batch time " + (end - start) / 1000.0 + "s");
// System.out.println("Total processed " + totalProcessed);
}
catch (Throwable t)
{
Throwable cause = getRootCause(t);
if (query.getHyperGraph().getStore().getTransactionFactory().canRetryAfter(cause))
continue;
try { query.getHyperGraph().getTransactionManager().endTransaction(false); }
catch (Throwable tt) { tt.printStackTrace(System.err); }
throw new RuntimeException(t);
}
finally
{
closeNoException(rs);
}
}
}
public static void directoryRecurse(File top, Mapping<File, Boolean> mapping)
{
File[] subs = top.listFiles();
if (subs != null)
{
for(File sub : subs)
{
if (sub.isDirectory())
directoryRecurse(sub, mapping);
mapping.eval(sub);
}
mapping.eval(top);
}
}
/**
* <p>
* Delete a {@link HyperGraph} by removing the filesystem directory that holds it.
* This method will first make sure to close the HyperGraph if it's currently open.
* </p>
*
* @param location The location of the graph instance.
*/
public static void dropHyperGraphInstance(String location)
{
if (HGEnvironment.isOpen(location))
{
HGEnvironment.get(location).close();
}
directoryRecurse(new File(location),
new Mapping<File, Boolean>()
{
public Boolean eval(File f)
{
return f.delete();
}
}
);
}
@SuppressWarnings("unchecked")
public static <T> Class<T> getImplementationClass(String interfaceClassName, String defaultImplementation)
{
try
{
String className = System.getProperty(interfaceClassName);
if (className == null)
className = defaultImplementation;
return (Class<T>)Class.forName(className);
}
catch (Exception ex)
{
throw new HGException(ex);
}
}
public static <T> T getImplementationOf(String interfaceClassName, String defaultImplementation)
{
Class<T> cl = getImplementationClass(interfaceClassName, defaultImplementation);
try
{
return cl.newInstance();
}
catch (Exception e)
{
throw new HGException(e);
}
}
@SuppressWarnings("unchecked")
public static <T> T cloneObject(T p, Mapping<Pair<Object, String>, Boolean> propertyFilter) throws Exception
{
if (p == null)
return null;
if (p instanceof Cloneable)
{
Method cloneMethod = p.getClass().getMethod("clone", (Class[])null);
if (cloneMethod != null)
return (T)cloneMethod.invoke(p, (Object[])null);
}
else if (p.getClass().isArray())
{
Object [] A = (Object[])p;
Class<?> type = p.getClass();
Object [] ac = (Object[])Array.newInstance(type.getComponentType(), A.length);
for (int i = 0; i < A.length; i++)
ac[i] = cloneObject(A[i], propertyFilter);
return (T)ac;
}
else if (identityCloneClasses.contains(p.getClass()))
return p;
//
// Need to implement cloning ourselves. We do this by copying bean properties.
//
Constructor<?> cons = null;
try
{
cons = p.getClass().getConstructor((Class[])null);
}
catch (Throwable t)
{
return p;
}
Object copy = cons.newInstance((Object[])null);
if (p instanceof Collection)
{
Collection<Object> cc = (Collection<Object>)copy;
for (Object el : (Collection<?>)p)
cc.add(cloneObject(el, propertyFilter));
}
else if (p instanceof Map)
{
Map<Object, Object> cm = (Map<Object, Object>)copy;
for (Object key : ((Map<Object, Object>)p).keySet())
cm.put(key, cloneObject(((Map<Object, Object>)p).get(key), propertyFilter));
}
else
{
BeanInfo bean_info = Introspector.getBeanInfo(p.getClass());
PropertyDescriptor beanprops [] = bean_info.getPropertyDescriptors();
if (beanprops == null || beanprops.length == 0)
copy = p;
else for (PropertyDescriptor desc : beanprops)
{
Method rm = desc.getReadMethod();
Method wm = desc.getWriteMethod();
if (rm == null || wm == null)
continue;
Object value = rm.invoke(p, (Object[])null);
if (propertyFilter == null || propertyFilter.eval(new Pair<Object, String>(p, desc.getName())))
value = cloneObject(value, propertyFilter);
wm.invoke(copy, new Object[] { value });
}
}
return (T)copy;
}
static final Set<Class<?>> identityCloneClasses = new HashSet<Class<?>>();
static
{
identityCloneClasses.add(String.class);
identityCloneClasses.add(Byte.class);
identityCloneClasses.add(Short.class);
identityCloneClasses.add(Integer.class);
identityCloneClasses.add(Long.class);
identityCloneClasses.add(Float.class);
identityCloneClasses.add(Double.class);
identityCloneClasses.add(Boolean.class);
identityCloneClasses.add(Character.class);
}
}