package com.openMap1.mapper.query;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;
import com.openMap1.mapper.AssocMapping;
import com.openMap1.mapper.ObjMapping;
import com.openMap1.mapper.core.ClassSet;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.mapping.AssociationMapping;
import com.openMap1.mapper.mapping.objectMapping;
import com.openMap1.mapper.mapping.propertyConversion;
import com.openMap1.mapper.mapping.propertyMapping;
import com.openMap1.mapper.reader.MDLXOReader;
import com.openMap1.mapper.reader.XOReader;
public class QueryStrategyImpl implements QueryStrategy{
private QueryParser parser;
private Vector<Vector<QueryClass>> allStrategies = new Vector<Vector<QueryClass>>();
private Vector<QueryClass> bestStrategy;
//----------------------------------------------------------------------------------
// Constructor
//----------------------------------------------------------------------------------
/** a query strategy consists just of the order in which the various
classes in the query are visited.
*/
public QueryStrategyImpl(QueryParser qParse)
{
this.parser = qParse;
}
//----------------------------------------------------------------------------------
// Defining the Strategy
//----------------------------------------------------------------------------------
public void defineStrategy() throws MapperException, QueryStrategyException
{
Vector<Vector<QueryClass>> current = firstPartialStrategy(parser.queryClasses());
// progressively make all merges of groups which can be made from link associations only
boolean progress = true;
while (progress)
{
Vector<Vector<QueryClass>> next = growGroups(current);
progress = (next.size() < current.size());
current = next;
}
// if there is only one list of QueryClasses connected by link associations, that list is the strategy.
if (current.size() == 1) bestStrategy = current.get(0);
/* if there is more than one list of QueryClasses connected by link associations,
* merge the lists in order and check you have got them all */
else if (current.size() > 1)
{
bestStrategy = mergeInFollowOrder(current);
if (bestStrategy.size() < parser.queryClasses().size())
{
String error = ("Best strategy has " + bestStrategy.size() + " classes out of " + parser.queryClasses().size());
message(error);
throw new MapperException(error);
}
}
}
/**
* refine a partial query strategy, by grouping the query classes into fewer and larger groups
* @param previous
* @return
*/
private Vector<Vector<QueryClass>> growGroups(Vector<Vector<QueryClass>> previous) throws MapperException
{
Vector<Vector<QueryClass>> result = new Vector<Vector<QueryClass>>();
for (int g = 0; g < previous.size();g++)
{
boolean found = false;
Vector<QueryClass> thisGroup = previous.get(g);
LinkAssociation link = linkLeadingTo(thisGroup.get(0));
/* there is a link leading to the head of this group. See if that link comes from any previous group in the result */
if (link != null)
{
QueryClass start = link.startClass();
for (int gg = 0; gg < result.size(); gg++) if (!found)
{
Vector<QueryClass> resultGroup = result.get(gg);
for (int c = 0; c < resultGroup.size();c++) if (!found)
{
QueryClass resClass = resultGroup.get(c);
if (resClass.matches(start))
{
found = true;
// merge this group on the tail of the result group
for (int i = 0; i < thisGroup.size();i++) resultGroup.add(thisGroup.get(i));
}
}
}
}
/* this group has not been merged to the tail of any other group, so is still a distinct group.
* See if the heads of any existing groups in the result are targets of links from this group.
* More than one group may pass this test, but only the first to pass is merged.
* Others will be caught on later calls of growGroups.*/
for (int gg = 0; gg < result.size(); gg++) if (!found)
{
Vector<QueryClass> resultGroup = result.get(gg);
LinkAssociation resultLink = linkLeadingTo(resultGroup.get(0));
if (resultLink != null)
{
QueryClass start = resultLink.startClass();
for (int t = 0; t < thisGroup.size(); t++) if (!found)
{
QueryClass cand = thisGroup.get(t);
if (cand.matches(start))
{
found = true;
// merge the result group on the tail of this group
for (int r = 0; r < resultGroup.size();r++) thisGroup.add(resultGroup.get(r));
// remove the result group from the result (OK as we will not iterate further over the result)
result.remove(resultGroup);
//add this enlarged group to the result
result.add(thisGroup);
}
}
}
}
/* this group has not been merged with any other group, so add it to the list of groups */
if (!found) result.add(thisGroup);
}
return result;
}
/**
* @param queryClasses
* @return a Vector of Vectors of QueryClasses,
* where each inner Vector has one element
*/
private Vector<Vector<QueryClass>> firstPartialStrategy(Vector<QueryClass> queryClasses)
{
Vector<Vector<QueryClass>> noStrat = new Vector<Vector<QueryClass>>();
for (int c = 0; c < queryClasses.size();c++)
{
Vector<QueryClass> vq = new Vector<QueryClass>();
vq.add(queryClasses.get(c));
noStrat.add(vq);
}
return noStrat;
}
/**
* FIXME - this algorithm does not yet work for hops backwards in the list -
* an esoteric case; but might need to increase the multi-pass limit
* @param groups
* @return
*/
private Vector<QueryClass> mergeInFollowOrder(Vector<Vector<QueryClass>> groups)
{
// set up the first group as seed, to grow a merged list of all groups from
Vector<QueryClass> result = groups.get(0);
boolean[] isMerged = new boolean[groups.size()];
isMerged[0] = true;
for (int g = 1; g < groups.size();g++) isMerged[g] = false;
// can only merge new groups if they connect to already merged groups. Multi-pass may be needed
for (int pass = 0; pass < 3; pass++)
{
for (int g1 = 0; g1 < groups.size(); g1++) if (isMerged[g1])
{
// group1 has already been merged into the result
Vector<QueryClass> group1 = groups.get(g1);
// try all different groups g2 which have not yet been merged
for (int g2 = 0; g2 < groups.size(); g2++) if ((g2 != g1) && (!isMerged[g2]))
{
Vector<QueryClass> group2 = groups.get(g2);
// if group 1 can follow group 2, and group 2 has not yet been merged, put group 2 first
if (canFollow(group1,group2))
{
isMerged[g2] = true;
result = vAppend(group2,result);
}
// if group 2 can follow group 1, and group 2 has not yet been merged, put group 2 last
else if (canFollow(group2,group1))
{
isMerged[g2] = true;
result = vAppend(result,group2);
}
}
}
}
return result;
}
private Vector<QueryClass> vAppend(Vector<QueryClass> v1, Vector<QueryClass> v2)
{
Vector<QueryClass> res = v1;
for (int i = 0; i < v2.size();i++) res.add(v2.get(i));
return res;
}
/**
*
* @param group1
* @param group2
* @return true is there is a cross-condition linking the head of group 1 to any class in group 2,
* so that group1 may follow group2 in the strategy list
*/
private boolean canFollow(Vector<QueryClass> group1, Vector<QueryClass> group2)
{
boolean isConnected = false;
for (int i = 0; i < parser.conditions().size();i++)
{
QueryCondition qc = parser.conditions().get(i);
if ((!qc.isConstantCondition()) && (qc.relation().equals("=")))
{
// see if this cross-condition links the head class of group1 to any class of group2
for (int g2 = 0; g2 < group2.size(); g2++)
{
if ((qc.queryClass().matches(group1.get(0))) && (qc.otherQueryClass().matches(group2.get(g2)))) isConnected = true;
if ((qc.queryClass().matches(group2.get(g2))) && (qc.otherQueryClass().matches(group1.get(0)))) isConnected = true;
}
}
}
return isConnected;
}
/**
* define a query strategy - method superseded.
*/
public void defineStrategy_old() throws MapperException, QueryStrategyException {
// if the natural ordering of query classes from the parser is a viable strategy, use it
if (isViableStrategy(parser.queryClasses()))
{
bestStrategy = new Vector<QueryClass>();
for (int i = 0; i < parser.queryClasses().size();i++)
bestStrategy.add(parser.queryClasses().get(i));
}
// otherwise, do an exhaustive search of all strategies (may be costly with many query classes)
else
{
// message("Exhaustive search of all query strategies for " + parser.queryClasses().size() + " query classes");
// find all possible strategies
for (Iterator<QueryClass> it = parser.queryClasses().iterator(); it.hasNext();)
{
QueryClass startClass = it.next();
Vector<QueryClass> partialStrategy = new Vector<QueryClass>();
partialStrategy.add(startClass);
extendStrategy(partialStrategy);
}
// choice of best strategy - currently just choose the first
if (allStrategies.size() > 0)
{
bestStrategy = allStrategies.get(0);
}
else throw new QueryStrategyException("Could not find a viable query strategy linking all classes");
}
}
/**
* a query strategy (an ordering of the QueryClass objects) is a viable strategy if
* (1) the first queryClass has no LinkAssociations leading to it
* (2) for every other QueryClass in the strategy, the one LinkAssociation leading to it comes from
* another QueryClass which has already appeared earlier in the strategy
* @param strategy
* @return
*/
private boolean isViableStrategy(Vector<QueryClass> strategy) throws MapperException
{
boolean viable = true;
// the first class in the strategy must have no links leading to it
if (linkLeadingTo(strategy.get(0)) != null) viable = false;
// all later classes in the strategy must have a link from an earlier class in the strategy
else for (int i = 1; i < strategy.size();i++) if (viable)
{
LinkAssociation link = linkLeadingTo(strategy.get(i));
if (link == null)
{
message("Query class " + strategy.get(i).identifier() + " has no links to it in strategy " + strategyString(strategy));
viable = false;
}
else
{
boolean foundStart = false;
for (int k = 0; k < i; k++)
{
if (link.startClass().equals(strategy.get(k))) foundStart = true;
}
if (!foundStart) viable = false;
}
}
return viable;
}
/**
*
* @param qc
* @return the one link association leading to a query class - or null if there are none
* @throws MapperException
*/
private LinkAssociation linkLeadingTo(QueryClass qc) throws MapperException
{
int foundLinks = 0;
LinkAssociation leadingTo = null;
for (Enumeration<LinkAssociation> en = parser.linkAssociations().elements();en.hasMoreElements();)
{
LinkAssociation next = en.nextElement();
if (next.endClass().equals(qc))
{
foundLinks++;
if (foundLinks > 1)
throw new MapperException("Query class " + qc.identifier() + " has more than one link association leading to it");
else leadingTo = next;
}
}
return leadingTo;
}
/**
* recursive extension of a query strategy to include new query classes
* @param partialStrategy
*/
private void extendStrategy(Vector<QueryClass> partialStrategy)
{
// message("Extending strategy " + summary(partialStrategy));
// if the partial strategy is complete, save it as a candidate strategy
if (partialStrategy.size() == parser.queryClasses().size()) allStrategies.add(partialStrategy);
// otherwise recursively extend it by one class at a time
else
{
// try all classes already in the strategy
for (Iterator<QueryClass> it = partialStrategy.iterator();it.hasNext();)
{
QueryClass nextClass = it.next();
// find an association from this class to a class not yet in the strategy
for (Enumeration<LinkAssociation> en = parser.linkAssociations().elements();en.hasMoreElements();)
{
LinkAssociation la = en.nextElement();
if (la.startClass().equals(nextClass))
{
QueryClass endClass = la.endClass();
// keep on extending the extended strategy, if the new class is not already in the strategy
if (!inStrategy(endClass,partialStrategy))
extendStrategy(addOneClass(partialStrategy,endClass));
}
}
}
}
}
/**
* @param aClass
* @param classes
* @return true if the class aClass is already in the partial strategy strategy
*/
private boolean inStrategy(QueryClass aClass, Vector<QueryClass> classes)
{
boolean inStrategy = false;
for (Iterator<QueryClass> it = classes.iterator();it.hasNext();)
if (it.next().equals(aClass)) inStrategy = true;
return inStrategy;
}
/**
*
* @param strategy
* @param nextClass
* @return a new partial strategy , got by adding the class to the strategy
*/
private Vector<QueryClass> addOneClass(Vector<QueryClass> strategy, QueryClass nextClass)
{
Vector<QueryClass> newStrategy = new Vector<QueryClass>();
for (Iterator<QueryClass> it = strategy.iterator();it.hasNext();)
newStrategy.add(it.next());
newStrategy.add(nextClass);
return newStrategy;
}
//----------------------------------------------------------------------------------
// New Access methods
//----------------------------------------------------------------------------------
/**
* @return ordered list of QueryClasses for the best strategy
*/
public Vector<QueryClass> bestStrategy() {return bestStrategy;}
/**
* @param order
* @return the single link association leading to the QueryClass at position order in the best strategy
*/
public LinkAssociation getLink(int order)
{
LinkAssociation link = null;
if ((order > 0) && (order < bestStrategy.size()))
{
QueryClass target = bestStrategy.get(order);
for (Enumeration<LinkAssociation> en = parser.linkAssociations().elements();en.hasMoreElements();)
{
LinkAssociation next = en.nextElement();
if (target. equals(next.endClass())) link = next;
}
}
return link;
}
//----------------------------------------------------------------------------------
// Old Access methods
//----------------------------------------------------------------------------------
public int nClasses() {
return parser.queryClasses().size();
}
public String old_strategyClass(int order) {
return bestStrategy.get(order).className();
}
public String[] old_linkAssociation(String className,
Vector<String> previousClasses, QueryParser qp)
throws QueryStrategyException {
// TODO Auto-generated method stub
return null;
}
private void message(String s) {System.out.println(s);}
private String summary(Vector<QueryClass> strategy)
{
String summary = "";
for (int i = 0; i < strategy.size(); i++) summary = summary + strategy.get(i).className() + " ";
return summary;
}
/**
* (used directly by FHIRSearchManager, indirectly in the query tool
* @param code
* @param mdl
* @throws MapperException
*/
public void setSubsets(String code, MDLXOReader mdl) throws MapperException
{
// find the unique object mapping for the start class of the strategy
QueryClass startClass = bestStrategy.get(0);
String startClassName = startClass.className();
Vector<objectMapping> startMappings = mdl.objectMappings(startClassName);
if (startMappings.size() != 1) throw new MapperException(startMappings.size() + " mappings for query strategy start class '" + startClassName + "'");
bestStrategy.get(0).setMapping(code, (ObjMapping)startMappings.get(0).map());
// find mappings for all other classes and link associations in the strategy, by following association names through mappings
for (int c = 1; c < bestStrategy.size();c++)
{
QueryClass nextClass = bestStrategy.get(c);
LinkAssociation nextLink = getLink(c);
// case where this QueryClass is joined to some previous Queryclass in the strategy by a link association
if (nextLink != null)
{
QueryClass sourceClass = nextLink.startClass();
String currentSubset = sourceClass.getSubset(code);
// if there is no mapping to the source class, do not try to follow the link
if (currentSubset != null)
{
// find all mappings between the two classes
Vector<AssociationMapping> mappings = mdl.getMappings(sourceClass.className(), nextClass.className());
int found = 0;
String nextClassName = null;
String nextSubset = null;
AssociationMapping assocMap = null;
// filter mappings by current subset and association name; 0 or 1 mappings should survive
for (int m = 0; m < mappings.size(); m++)
{
AssociationMapping next = mappings.get(m);
/* test the subset at end 0, and then use the subset at end 1 to find the next class mapping;
* FIXME need to generalise when link associations can be followed from end 1 to end 0*/
if ((next.assocEnd(0).subset().equals(currentSubset)) && (next.assocName().equals(nextLink.assocName())))
{
found++;
nextClassName = next.assocEnd(1).className();
nextSubset = next.assocEnd(1).subset();
assocMap = next;
}
}
if (found > 1) throw new MapperException("Found " + found + " links "
+ sourceClass.className() + "." + nextLink.assocName() + "." + nextClass.className());
if (found == 1)
{
objectMapping om = mdl.namedObjectMapping(new ClassSet(nextClassName,nextSubset));
nextClass.setMapping(code, (ObjMapping)om.map());
nextLink.setMapping(code, (AssocMapping)assocMap.map());
}
} // end of case (currentSubset != null)
} // end of case (nextLink != null)
// case where this QueryClass is joined to some previous QueryClass in the strategy not by a LinkAssociation, but by a cross-condition
else if (nextLink == null)
{
/* in this case there is nothing in the mappings to constrain the subset of the class,
* but there is actually no need to; the query executor will get all objectReps of any subset. */
Vector<objectMapping> oMaps = mdl.objectMappings(nextClass.className());
// if (oMaps.size() > 1) throw new MapperException("Found " + oMaps.size() + " object mappings for class " + nextClass.className());
// I don't think this serves any purpose
if (oMaps.size() == 1) nextClass.setMapping(code, (ObjMapping)oMaps.get(0).map());
}
} // end of loop over QueryClasses in the strategy
// find all property mappings for each write field in the query - taking account of property conversions
for (int f = 0; f < parser.writeFields().size(); f++)
{
WriteField field = parser.writeFields().get(f);
String className = field.queryClass().className();
String subset = field.queryClass().getSubset(code);
String propName = field.propName();
if (subset != null) // do nothing if this object is not mapped in the data source
{
ClassSet cSet = new ClassSet(className,subset);
storeRequiredMappings(field, cSet, propName, mdl, code);
}
}
// find all property mappings for each query condition in the query - taking account of property conversions
for (int q = 0; q < parser.conditions().size(); q++)
{
QueryCondition field = parser.conditions().get(q);
// code for the LHS of the condition
String className = field.queryClass().className();
String subset = field.queryClass().getSubset(code);
String propName = field.propName();
if (subset != null) // do nothing if this object is not mapped in the data source
{
ClassSet cSet = new ClassSet(className,subset);
storeRequiredMappings(field, cSet, propName, mdl, code);
}
// code for RHS of the condition, if variable
if (!field.isConstantCondition())
{
className = field.otherQueryClass().className();
subset = field.otherQueryClass().getSubset(code);
propName = field.otherPropName();
if (subset != null) // do nothing if this object is not mapped in the data source
{
ClassSet cSet = new ClassSet(className,subset);
storeRequiredMappings(field, cSet, propName, mdl, code);
}
}
}
}
/**
* define the mapping subsets for each queryClass in a DataSource (used in the query tool)
*/
public void setSubsets(DataSource ds) throws MapperException
{
XOReader reader = ds.getReader();
String code = ds.getCode();
// currently can only find subsets in this way for mapped readers; this is sufficient for mapped RDBs.
if (reader instanceof MDLXOReader)
{
MDLXOReader mdl = (MDLXOReader)reader;
setSubsets(code,mdl);
}
}
/**
* store all mappings required for a write field, recursing through any property conversions
* @param field
* @param cSet
* @param propName
* @param mdl
* @param code
* @return true if the property mapping is direct, not through property conversions
* @throws MapperException
*/
private boolean storeRequiredMappings(QueryMappingUser field, ClassSet cSet, String propName, MDLXOReader mdl, String code) throws MapperException
{
boolean direct = false;
Vector<propertyMapping> pms = mdl.namedPropertyMappings(cSet.className(),cSet.subset(),propName);
propertyConversion pc = mdl.getInConversion(cSet,propName);
// when the property is directly mapped in the data source, with no conversions
if (pms.size() > 0)
{
/* more than one property mapping can only occur for multiway and choice mappings, which we ignore pro tem */
propertyMapping mainPropMapping = pms.get(0);
field.addMappings(code, mainPropMapping);
direct = true;
}
// if this is the result of a property conversion
else if ((pc != null) && (pc.hasImplementation("Java")) && (pc.canDoJavaConvert()))
{
Vector<String> argVect = pc.arguments();
for (int k = 0; k < argVect.size(); k++)
{
String pseudoProp = argVect.elementAt(k);
storeRequiredMappings(field,cSet,pseudoProp,mdl,code);
}
}
return direct;
}
/**
* return a String representation of a strategy
*/
private String strategyString(Vector<QueryClass> strategy)
{
String strat = "[";
for (int s = 0; s < strategy.size();s++)
{
QueryClass qc = strategy.get(s);
strat = strat + qc.className();
if (s < strategy.size() -1) strat = strat + ", ";
}
strat = strat + "]";
return strat;
}
}