/*
* Copyright 2006 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import ome.model.containers.Dataset;
import ome.model.containers.DatasetImageLink;
import ome.model.containers.Project;
import ome.model.containers.ProjectDatasetLink;
import ome.model.core.Image;
import org.hibernate.Criteria;
import org.hibernate.transform.BasicTransformerAdapter;
import org.hibernate.transform.ResultTransformer;
/**
* single-point of entry for walking of OME container hierarchies.
*
*
* @author Josh Moore, josh at glencoesoftware.com
* @since OMERO 3.0
*/
public class Hierarchy {
//TODO Nodes currently uses statically defined arrays containing the
//Hierarchy information. This needs to be refactored to use ITypes.
/**
* The rather complicated data structures are used to efficiently produce
* chains which represent a walk up from Image to higher-level containers,
* or a walk down from any container to Image.
*
* klass and depth are fairly self-descriptive. child and parent represent
* the name of the fields which lead down and up, respectively. ptr is a
* pointer to the index in the other arrays of the child. image (without a
* child) is -1.
*/
static class Nodes {
static Class[] klass = new Class[] { Project.class, Dataset.class,
Image.class };
static int[] depth = new int[] { 2, 1, 0, 1, 2 };
static Class[] link = new Class[] { ProjectDatasetLink.class,
DatasetImageLink.class, null };
static String[] child = new String[] { "datasetLinks", "imageLinks",
null, "imageLinks", "categoryLinks" };
static String[] parent = new String[] { "projectLinks", "datasetLinks",
null, "categoryLinks", "categoryGroupLinks" };
static int[] ptr = new int[] { 1, 2, -1, 2, 3 };
/** pointer to the right index in the arrays */
private static Map<Class, Integer> lookup = new HashMap<Class, Integer>();
static {
lookup.put(Project.class, 0);
lookup.put(Dataset.class, 1);
lookup.put(Image.class, 2);
}
/**
* uses the {@link #lookup} map to get the proper index. Values less
* than zero signal an unknown class.
*/
static int lookup(Class k) {
int i = lookup.containsKey(k) ? lookup.get(k).intValue() : -1;
return i;
}
/**
* calls {@link #lookup(Class)} and throws an exception if the value is
* less than 0.
*/
static int lookupWithError(Class k) {
int i = lookup(k);
if (i < 0) {
throw new IllegalArgumentException("Unknown class:" + k);
}
return i;
}
/**
* a container is defined to be any known class (see {@link #lookup})
* which is not an {@link Image}
*/
static boolean isContainer(Class k) {
int i = lookup(k);
if (i < 0) {
return false;
}
if (Image.class.isAssignableFrom(klass[i])) {
return false;
}
return true;
}
// Walking hierarchy
static LinkedList<Integer> getList(Class k) {
int i = lookupWithError(k);
LinkedList<Integer> retVal = new LinkedList<Integer>();
if (!isContainer(k)) {
return retVal;
}
retVal.add(i);
retVal.addAll(Nodes.getList(klass[ptr[i]]));
return retVal;
}
// Produce lists of link names for walking the graph
static List<String> pathToParent(Class k) {
int i = lookupWithError(k);
LinkedList<Integer> list = getList(k);
List<String> retVal = new ArrayList<String>();
for (int j = list.size() - 1; j >= 0; j--) {
retVal.add(parent[list.get(j)]);
}
return retVal;
}
static List<String> pathToChildFrom(Class k) {
int i = lookupWithError(k);
LinkedList<Integer> list = getList(k);
List<String> retVal = new ArrayList<String>();
for (int j = 0; j < list.size(); j++) {
retVal.add(child[list.get(j)]);
}
return retVal;
}
// Produce map of aliases to class names for ResultTransformer
static Map<String, String> childMap(Class k) {
Map<String, String> map = new HashMap<String, String>();
LinkedList<Integer> list = getList(k);
buildMap(k, map, list);
return map;
}
static Map<String, String> parentMap(Class k) {
Map<String, String> map = new HashMap<String, String>();
LinkedList<Integer> list = getList(k);
Collections.reverse(list);
buildMap(k, map, list);
return map;
}
private static void buildMap(Class k, Map<String, String> map,
LinkedList<Integer> list) {
int count = 0;
int current = -1;
current = list.poll();
assert k.equals(klass[current]);
map.put("this", k.getName());
while (list.size() > 0) {
current = list.poll();
count++;
map.put("genitem_" + count, klass[current].getName());
}
}
}
public static Criteria[] fetchParents(Criteria c, Class klass, int stopDepth) {
if (!Nodes.isContainer(klass)) {
throw new IllegalStateException(
"Invalid class for parent hierarchy:" + klass);
}
return walk(c, klass, Nodes.pathToParent(klass), "parent", stopDepth,
Query.LEFT_JOIN);
}
public static Criteria[] fetchChildren(Criteria c, Class klass,
int stopDepth) {
if (!Nodes.isContainer(klass)) {
throw new IllegalStateException(
"Invalid class for child hierarchy:" + klass);
}
return walk(c, klass, Nodes.pathToChildFrom(klass), "child", stopDepth,
Query.LEFT_JOIN);
}
// TODO used?
public static Criteria[] joinParents(Criteria c, Class klass, int stopDepth) {
if (!Nodes.isContainer(klass)) {
throw new IllegalStateException(
"Invalid class for parent hierarchy:" + klass);
}
return walk(c, klass, Nodes.pathToParent(klass), "parent", stopDepth,
Query.INNER_JOIN);
}
public static Criteria[] joinChildren(Criteria c, Class klass, int stopDepth) {
if (!Nodes.isContainer(klass)) {
throw new IllegalStateException(
"Invalid class for child hierarchy:" + klass);
}
return walk(c, klass, Nodes.pathToChildFrom(klass), "child", stopDepth,
Query.INNER_JOIN);
}
private static Criteria[] walk(Criteria c, Class k, List<String> links,
String step, int stopDepth, int joinStyle) {
int index = Nodes.lookup(k);
int depth = Math.min(stopDepth, Nodes.depth[index]);
String[][] path = new String[2][2];
Criteria[] retVal = new Criteria[depth * 2];
for (int i = 0; i < depth; i++) {
path[i][0] = (i > 0 ? path[i - 1][1] + "." : "") + links.get(i);
path[i][1] = path[i][0] + "." + step;
}
switch (depth) {
case 2:
retVal[3] = c.createCriteria(path[1][1], "genitem_2", joinStyle);
retVal[2] = c.createCriteria(path[1][0], "genlink_2", joinStyle);
case 1:
retVal[1] = c.createCriteria(path[0][1], "genitem_1", joinStyle);
retVal[0] = c.createCriteria(path[0][0], "genlink_1", joinStyle);
case 0:
return retVal;
default:
throw new RuntimeException("Unhandled container depth.");
}
}
public static ResultTransformer getChildTransformer(Class klass) {
Map trans = Nodes.childMap(klass);
return new HierarchyToMapTransformer(trans);
}
public static ResultTransformer getParentTransformer(Class klass) {
Map trans = Nodes.parentMap(klass);
return new HierarchyToMapTransformer(trans);
}
}
/**
* transforms a hierarchy to a map by using the aliases provided to the
* constructor.
*
* @see ome.services.query.PojosCGCPathsQueryDefinition
*
*/
class HierarchyToMapTransformer extends BasicTransformerAdapter {
/**
*
*/
private static final long serialVersionUID = -3530890859099882786L;
Map _aliases;
HierarchyToMapTransformer(Map aliases) {
this._aliases = aliases;
}
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Map result = new HashMap();
for (int i = 0; i < tuple.length; i++) {
String alias = aliases[i];
if (alias != null && _aliases.containsKey(alias)) {
result.put(_aliases.get(alias), tuple[i]);
}
}
return result;
}
}