B. ADQL tree Part 2

In order to help the navigation and the manipulation in the tree generated by the parser, the library provides two useful features...

ADQLIterator

Any object into ADQLQuery (including itself and its clauses) has a function called adqlIterator(). This function lets iterate on all ADQL objects (ADQLObject) contained into the target object of the function. It is particularly true for the clauses where this function returns the same items as their function iterator():

ADQLIterator it = query.getSelect().adqlIterator();
System.out.println("ADQL objects into the SELECT clause:");
while(it.hasNext())
	System.out.println("  * "+it.next().toADQL());
Warning!

In the case of the clause FROM, adqlIterator() returns all ADQLTable, ADQLJoin, ADQLColumn and ADQLQuery.

Thanks to adqlIterator(), it is now possible to browse the tree node by node so that returning objects that match a given condition...as illustrated below :)

UML class diagram of ADQLIterator.

Search handlers

The interface ISearchHandler defines the main API for any search handler:

The library provides a simple abstract implementation of this interface so that managing by default the matching objects and the navigation into the tree from the given node: SimpleSearchHandler. This implementation, uses the function adqlIterator() to browse all the ADQL objects contained into the given object. It collects all ADQL objects for which the function match(ADQLObject) returns true. All matching items are collected into an array list so that being returned after the call of search(ADQLObject) thanks to iterator().

With SimpleSearchHandler you can decide to stop the research at the first matching object to make a recursive research. "Recursive research" means that the research will continue into any internal ADQLQuery. For instance, if you search all ADQL objects into the clause FROM of this query:

SELECT *
FROM A JOIN (SELECT id FROM B WHERE foo BETWEEN 1 AND 3.7) C ON A.oid = C.oid
WHERE bar = 'stuff';

The research will return: A, id, B, foo, A.oid, C.oid, (SELECT id FROM B WHERE foo BETWEEN 1 AND 3.7), SELECT id, ...

Why "Simple"SearchHandler?

This abstract implementation is qualified Simple because results are simply collected into an array list. But we could image a TreeSearchHandler which keeps the whole path from the given object to a matching object. Simple only refers to the way matching items are retained and then made accessible: a list with no effort on ordering or on keeping link with the ADQL query.

UML class diagram of the interface ISearchHandler.

Make your own SearchHandler: SearchColumnHandler & SearchPointHandler

To make your own search handlers, you can either implement the interface ISearchHandler or extend the class SimpleSearchHandler. The second option is the most suitable if you do not need more information about the matching objects than the matching objects themselves. Indeed, you just have to override the function match(ADQLObject) to give the search condition.

For instance, if you need to search all column references:

public class SearchColumnHandler extends SimpleSearchHandler {

	public SearchColumnHandler() {
		super();
	}

	public SearchColumnHandler(boolean recursive) {
		super(recursive);
	}

	public SearchColumnHandler(boolean recursive, boolean onlyFirstMatch) {
		super(recursive, onlyFirstMatch);
	}

	public boolean match(ADQLObject obj) {
		return (obj instanceof ADQLColumn);
	}

}
Class already in the library!

SearchColumnHandler is already provided in the library.

UML class diagram of SearchColumnHandler.

Obviously you can imagine more complex research criteria, as in the following example. Here, we search for all POINT functions corresponding to a position of our table data. In other words: all the positions whose the coordinates are data.ra and data.dec.

public class SearchPointHandler extends SimpleSearchHandler {
	public SearchPointHandler() { super(); }
	public SearchPointHandler(boolean recursive) { super(recursive); }
	public SearchPointHandler(boolean recursive, boolean onlyFirstMatch) { super(recursive, onlyFirstMatch); }

	/* Only the POINT functions which use the 2 columns of the table "data": "ra" and "dec".*/
	public boolean match(ADQLObject obj) {
		try{
			PointFunction point = (PointFunction)obj;
			ADQLColumn col1 = (ADQLColumn)point.getCoord1(), col2 = (ADQLColumn)point.getCoord2();
			if (col1.getDBLink() != null && col2.getDBLink() != null){
				// if their table is "data":
				if (col1.getDBLink().getTable().getDBName().equalsIgnoreCase("data")
				    && col1.getDBLink().getTable().getDBName().equals(col2.getDBLink().getTable().getDBName())){
					// "true" if the 1st coordinate is "ra" and the 2nd "dec":
					return col1.getColumnName().equalsIgnoreCase("ra")
					       && col2.getColumnName().equalsIgnoreCase("dec");
				}
			}
		}catch(ClassCastException cce){;}
		return false;
	}
}

In addition of letting us browse the ADQL tree, ADQLIterator has the function replace(ADQLObject) which is able to replace objects inside the target object.

Replace handlers

The interface ISearchHandler has been extended so that adding the function searchAndReplace(ADQLQuery): IReplaceHandler. Similarly, the class SimpleSearchHandler has been extended to get SimpleReplaceHandler. This implementation has one additional abstract function: getReplacer(ADQLObject). This function must return the ADQL object which should replace the given one.

Don't want to replace a match?

If you do not want to replace an ADQL object, getReplacer(ADQLObject) must return the object given in parameter.

How to get the parent of an object?

There is no way to get the parent of a given ADQL object with a SimpleReplaceHandler! So, your search condition must be the object which will be modified or replaced. See the following examples.

RemoveHandler

If getReplacer(ADQLObject) returns null, the given object will be removed from its parent. It is exactly what does the class RemoveHandler.

UML class diagram of the interface IReplaceHandler.

Make your own ReplaceHandler: ReplacePointHandler

When extending SimpleReplaceHandler, you have to override two functions: match(ADQLObject) and getReplacer(ADQLObject).

Now, let's transform the previous example - SearchPointHandler - in ReplacePointHandler in order to replace all POINT(..., ra, dec) by the column coord:

public class ReplacePointHandler extends SimpleReplaceHandler {
	public ReplacePointHandler() { super(); }
	public ReplacePointHandler(boolean recursive) { super(recursive); }
	public ReplacePointHandler(boolean recursive, boolean onlyFirstMatch) { super(recursive, onlyFirstMatch); }

	/* Only the POINT functions which use the 2 columns of the table "data": "ra" and "dec".*/
	public boolean match(ADQLObject obj) {
		... // see SearchColumnHandler.match(ADQLObject)
	}
	
	/* Replace by: "data.coord" (or <tableAlias>".coord").  */
	public ADQLObject getReplacer(ADQLObject objToReplace) {
		try{
			PointFunction point = (PointFunction)objToReplace;
			ADQLColumn col1 = (ADQLColumn)point.getCoord1();
			if (col1.getAdqlTable() != null){
				String tableAlias = col1.getAdqlTable().getAlias();
				if (tableAlias != null)
					return new ADQLColumn(tableAlias, "coord");
				else
					return new ADQLColumn("basic_data", "coord");
			}else
				return new ADQLColumn("basic_data", "coord");
		}catch(ClassCastException cce){;}
		return null;
	}
}