/*
* 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.type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGHandle;
import org.hypergraphdb.HGPersistentHandle;
import org.hypergraphdb.HGSearchResult;
import org.hypergraphdb.HGValueLink;
import org.hypergraphdb.HyperGraph;
import org.hypergraphdb.algorithms.DefaultALGenerator;
import org.hypergraphdb.algorithms.HGDepthFirstTraversal;
import org.hypergraphdb.atom.HGSubsumes;
import org.hypergraphdb.query.AtomTypeCondition;
import org.hypergraphdb.query.TypePlusCondition;
import org.hypergraphdb.transaction.HGTransactionException;
import org.hypergraphdb.util.HGUtils;
/**
* <p>
* A set of static utility methods operating on types.
* </p>
*
* @author Borislav Iordanov
*/
public final class TypeUtils
{
/**
* <p>Split a dimension path in dot notation to a <code>String[]</code>.</p>
*
* @param formatted The dimension path in the format "a.b.c...d"
* @return The <code>String[]</code> {"a", "b", "c", ..., "d"}
*/
public static String [] parseDimensionPath(String formatted)
{
StringTokenizer tok = new StringTokenizer(formatted, ".");
String [] result = new String[tok.countTokens()];
for (int i = 0; i < result.length; i++)
result[i] = tok.nextToken();
return result;
}
public static String formatDimensionPath(String [] dimPath)
{
StringBuffer result = new StringBuffer();
for (int i = 0; i < dimPath.length; i++)
{
result.append(dimPath[i]);
if (i < dimPath.length - 1)
result.append(".");
}
return result.toString();
}
public static HGProjection getProjection(HyperGraph hg, HGAtomType type, String [] dimPath)
{
HGProjection proj = null;
for (int j = 0; j < dimPath.length; j++)
{
if (! (type instanceof HGCompositeType))
return null;
proj = ((HGCompositeType)type).getProjection(dimPath[j]);
if (proj == null)
return null;
type = (HGAtomType)hg.get(proj.getType());
}
return proj;
}
public static HGTypedValue project(HyperGraph hg,
HGHandle type,
Object value,
String [] dimPath,
boolean throwExceptionOnNotFound)
{
Object topvalue = value;
for (int j = 0; j < dimPath.length; j++)
{
HGAtomType tinstance = (HGAtomType)hg.get(type);
if (! (tinstance instanceof HGCompositeType))
if (throwExceptionOnNotFound)
throw new HGException("Only composite types can be indexed by value parts: " + value
+ ", during projection of " + formatDimensionPath(dimPath) +
" in type " + tinstance);
else
return null;
HGProjection proj = ((HGCompositeType)tinstance).getProjection(dimPath[j]);
if (proj == null)
if (throwExceptionOnNotFound)
throw new HGException("Dimension " + dimPath[j] +
" does not exist in type " + tinstance);
else
return null;
if (value == null)
throw new IllegalArgumentException("The value " + value +
" doesn't have a property at " + Arrays.asList(dimPath));
if (value instanceof HGValueLink)
value = ((HGValueLink)value).getValue();
value = proj.project(value);
type = proj.getType();
}
return new HGTypedValue(value, type);
}
// ------------------------------------------------------------------------
// The following part deals solely with the management of circular references
// during storage and retrieval of aggregate values. Type implementations
// would normally use those utility methods to track circular references.
//
// The implementation is straightforward: two maps and a set are maintained:
//
// - JAVA_REF_MAP maps pure Java references (Objects) to HGPersistentHandles.
// It is used during storage of aggregate values. It is attached to the
// current transaction as an attribute.
//
// - HANDLE_REF_MAP maps HGPersistentHandles to pure Java references. It is
// attached to the current thread (since reads are not necessarily enclosed
// in a transaction) and it is used during retrieval of aggregate values.
//
// - HANDLE_REF_SET contains the set of value handles that are being released.
// It is attached to the current transaction as an attribute.
//
// In all cases, type implementations are expected to consult the relevant
// map in order to see whether the operation (read, write, remove) has already been
// performed in the current context (transaction write or thread-local read).
//
// Obviously, this mechanism doesn't cross thread boundaries. As always,
// this limitation will be reexamined as the need arises.
//
// Another note: The duality of Record-JavaBeans forces a little hack. We really
// want to associate the Java bean instance (and not the temporary Record created
// just so that it is stored by the RecordType implementation) to the
// HGPersistentHandle of the value. This information, that we are actually tracking
// a bean reference, not a Record reference, must be passed from the wrapping type
// (a JavaBeanBinding) to the underlying type (RecordType) to finally the
// TypeUtils.getNewHandleFor method. The interface WrappedRuntimeInstance was
// created to solve this issue. Couldn't find a more elegant way that doesn't
// involve changing the HGAtomType interface. The problem is that it is the
// responsibility of the HGAtomType.store method to create the handle of the
// value. But RecordType.store is called with a Record instance, not a bean
// instance and therefore cannot do the mapping Object->HGPersistentHandle
// that we need to track circular references. Got it?
//
//
// [Borislav Iordanov]
//
// Addendum 10/24/2009 - Switched to using thread locals instead of transaction attributes
// for those because of the need to save separate copies of values for different atoms.
// Suppose we need to save atoms A and B both of which hold the same Java reference 'x'.
// If A and B are saved in a single transaction, the value of 'x' will be stored only
// once and shared b/w A and B. However, when one of A and B is subsequently deleted, the
// other will be left with a dangling UUID pointing to a removed 'x'. Another solution to
// this problem would have been reference counting across the board for everything in
// the HGStore, but that opens its own can of worms. Using thread locals is ok given the
// HGDB transactions API that encourages single-threaded transactions.
//
private static ThreadLocal<Map<HGPersistentHandle, Object>> HANDLE_REF_MAP =
new ThreadLocal<Map<HGPersistentHandle, Object>>();
private static ThreadLocal<Map<Object, HGPersistentHandle>> JAVA_REF_MAP =
new ThreadLocal<Map<Object, HGPersistentHandle>>();//hg.getTransactionManager().getContext().getCurrent().getAttribute(JAVA_REF_MAP);
private static ThreadLocal<Set<HGPersistentHandle>> HANDLE_REF_SET =
new ThreadLocal<Set<HGPersistentHandle>>();
public interface WrappedRuntimeInstance { Object getRealInstance(); }
// private static final String JAVA_REF_MAP = "JAVA_REF_VALUE_MAP".intern();
// private static final String HANDLE_REF_SET = "HANDLE_REF_SET".intern();
// private static final String HANDLE_REF_MAP = "HANDLE_REF_MAP".intern();
// public static Map<Object, HGPersistentHandle> getTransactionObjectRefMap(HyperGraph hg)
// {
// ThreadLocal<Map<Object, HGPersistentHandle>> refMap =
// (ThreadLocal<Map<Object, HGPersistentHandle>>)hg.getTransactionManager().getContext().getCurrent().getAttribute(JAVA_REF_MAP);
// if (refMap == null)
// {
// refMap = new ThreadLocal<Map<Object, HGPersistentHandle>>();
// hg.getTransactionManager().getContext().getCurrent().setAttribute(JAVA_REF_MAP, refMap);
// }
// if (refMap.get() == null)
// refMap.set(new IdentityHashMap<Object, HGPersistentHandle>());
// return refMap.get();
// }
// public static Map<HGPersistentHandle, Object> getThreadHandleRefMap(HyperGraph graph)
// {
// Map<HGPersistentHandle, Object> refMap =
// (Map<HGPersistentHandle, Object>)graph.getTransactionManager().getContext().getCurrent().getAttribute(HANDLE_REF_MAP);
// // HANDLE_REF_MAP.get();
// if (refMap == null)
// {
// refMap = new HashMap<HGPersistentHandle, Object>();
// graph.getTransactionManager().getContext().getCurrent().setAttribute(HANDLE_REF_MAP, refMap);
// }
// return refMap;
// }
// public static Set<HGPersistentHandle> getTransactionHandleSet(HyperGraph hg)
// {
// Set<HGPersistentHandle> set =
// (Set<HGPersistentHandle>)hg.getTransactionManager().getContext().getCurrent().getAttribute(HANDLE_REF_SET);
// if (set == null)
// {
// set = new HashSet<HGPersistentHandle>();
// hg.getTransactionManager().getContext().getCurrent().setAttribute(HANDLE_REF_SET, set);
// }
// return set;
// }
/**
* <b>Internal method - do not call.</b>
*/
public static boolean initThreadLocals()
{
if (JAVA_REF_MAP.get() == null)
{
JAVA_REF_MAP.set(new IdentityHashMap<Object, HGPersistentHandle>());
HANDLE_REF_MAP.set(new HashMap<HGPersistentHandle, Object>());
HANDLE_REF_SET.set(new HashSet<HGPersistentHandle>());
return true;
}
else
return false;
}
/**
* <b>Internal method - do not call.</b>
*/
public static void releaseThreadLocals(boolean topCall)
{
if (topCall)
{
JAVA_REF_MAP.set(null);
HANDLE_REF_MAP.set(null);
HANDLE_REF_SET.set(null);
}
}
public static HGPersistentHandle getNewHandleFor(HyperGraph hg, Object value)
{
boolean topCall = initThreadLocals();
try
{
HGPersistentHandle result = hg.getHandleFactory().makeHandle();
if (value instanceof WrappedRuntimeInstance)
value = ((WrappedRuntimeInstance)value).getRealInstance();
JAVA_REF_MAP.get().put(value, result);
return result;
}
finally
{
releaseThreadLocals(topCall);
}
}
public static boolean isValueReleased(HyperGraph graph, HGPersistentHandle h)
{
boolean topCall = initThreadLocals();
try
{
return HANDLE_REF_SET.get().contains(h);
}
finally
{
releaseThreadLocals(topCall);
}
}
public static void releaseValue(HyperGraph graph, HGAtomType type, HGPersistentHandle h)
{
boolean topCall = initThreadLocals();
try
{
// If this was previously added in the course of the current transaction,
// remove it from the relevant maps.
Object instance = HANDLE_REF_MAP.get().remove(h);
if (instance != null)
JAVA_REF_MAP.get().remove(instance);
if (! (type instanceof HGRefCountedType))
HANDLE_REF_SET.get().add(h);
type.release(h);
}
finally
{
releaseThreadLocals(topCall);
}
}
public static HGPersistentHandle storeValue(HyperGraph graph, Object value, HGAtomType type)
{
boolean topCall = initThreadLocals();
try
{
HGPersistentHandle result = null;
if (! (type instanceof HGRefCountedType))
result = JAVA_REF_MAP.get().get(value);
if (result == null)
{
result = type.store(value);
JAVA_REF_MAP.get().put(value, result);
}
HANDLE_REF_MAP.get().put(result, value);
return result;
}
finally
{
releaseThreadLocals(topCall);
}
}
public static HGPersistentHandle getHandleFor(HyperGraph graph, Object value)
{
boolean topCall = initThreadLocals();
try
{
return JAVA_REF_MAP.get().get(value);
}
finally
{
releaseThreadLocals(topCall);
}
}
public static Object getValueFor(HyperGraph graph, HGPersistentHandle h)
{
boolean topCall = initThreadLocals();
try
{
return HANDLE_REF_MAP.get().get(h);
}
finally
{
releaseThreadLocals(topCall);
}
}
public static void setValueFor(HyperGraph graph, HGPersistentHandle h, Object value)
{
boolean topCall = initThreadLocals();
try
{
Map<HGPersistentHandle, Object> refMap = HANDLE_REF_MAP.get();
if (!refMap.containsKey(h))
refMap.put(h, value);
}
finally
{
releaseThreadLocals(topCall);
}
}
public static Object makeValue(HyperGraph graph, HGPersistentHandle h, HGAtomType type)
{
boolean topCall = initThreadLocals();
try
{
Map<HGPersistentHandle, Object> refMap = HANDLE_REF_MAP.get();
Object result = refMap.get(h);
if (result == null)
{
result = type.make(h, null, null);
refMap.put(h, result);
}
return result;
}
finally
{
releaseThreadLocals(topCall);
}
}
public static List<HGHandle> subsumesClosure(HyperGraph graph, HGHandle baseType)
{
DefaultALGenerator alGenerator = new DefaultALGenerator(graph,
new AtomTypeCondition(HGSubsumes.class),
null,
false,
true,
false);
HGDepthFirstTraversal traversal = new HGDepthFirstTraversal(baseType, alGenerator);
ArrayList<HGHandle> subTypes = new ArrayList<HGHandle>();
while (traversal.hasNext())
subTypes.add(traversal.next().getSecond());
subTypes.add(baseType);
return subTypes;
}
public static boolean deleteType(HyperGraph graph, HGHandle th, boolean recursive)
{
if (recursive)
{
List<HGHandle> L = subsumesClosure(graph, th);
for (HGHandle h : L)
if (!deleteType(graph, h))
return false;
return true;
}
else
return deleteType(graph, th);
}
public static boolean deleteType(HyperGraph graph, Class<?> type, boolean recursive)
{
HGHandle th = graph.getTypeSystem().getTypeHandleIfDefined(type);
if (th == null)
return true;
else
return deleteType(graph, th, recursive);
}
public static boolean deleteType(HyperGraph graph, HGHandle type)
{
if (deleteInstances(graph, type))
return graph.remove(type);
else
return false;
}
public static boolean deleteInstances(HyperGraph graph, HGHandle type)
{
int batchSize = 100; // perhaps this should become a parameter
HGHandle [] batch = new HGHandle[batchSize];
for (boolean done = false; !done; )
{
HGSearchResult<HGHandle> rs = null;
try
{
int fetched = 0;
graph.getTransactionManager().beginTransaction();
try
{
rs = graph.find(new TypePlusCondition(type));
while (fetched < batch.length && rs.hasNext())
batch[fetched++] = rs.next();
rs.close();
}
finally
{
try { graph.getTransactionManager().endTransaction(true); }
catch (HGTransactionException tex) { throw new HGException(tex); }
}
done = fetched == 0;
graph.getTransactionManager().beginTransaction();
try
{
while (--fetched >= 0)
if (!graph.remove(batch[fetched]))
return false;
graph.getTransactionManager().endTransaction(true);
}
catch (Throwable t)
{
try { graph.getTransactionManager().endTransaction(false); }
catch (Throwable t1) { t1.printStackTrace(System.err); }
done = false;
HGUtils.wrapAndRethrow(t);
}
}
finally
{
HGUtils.closeNoException(rs);
}
}
return true;
}
}