/** * The contents of this file are subject to the Open Software License * Version 3.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.opensource.org/licenses/osl-3.0.txt * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. */ package org.mulgara.query.filter; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jrdf.graph.Node; import org.mulgara.query.QueryException; import static org.mulgara.util.ObjectUtil.eq; /** * A simple Context used for testing purposes. * This context returns <code>null</code> for unbound values, since it is globalizing. * @created Mar 31, 2008 * @author Paula Gearon * @copyright © 2008 <a href="mailto:pgearon@users.sourceforge.net">Paula Gearon</a> * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a> */ public class TestContext implements Context { /** The current internal row for the data */ private int rowNumber = -1; /** The names of the columns */ private List<String> columnNames; /** The rows of data. Now row can exceed the size of columnNames */ private Node[][] rows; /** The map for converting IDs into Nodes stored against them */ private Map<Long,Node> globalizer = new HashMap<Long,Node>(); /** The map for converting Nodes into the IDs stored against them */ private Map<Node,Long> localizer = new HashMap<Node,Long>(); /** The pseudo node-pool counter */ private long lastNode = 1; /** * Empty constructor used for tests which have no data. */ public TestContext() { columnNames = Collections.emptyList(); rows = new Node[][] { new Node[] {} }; } /** * Creates a test context. * @param columnNames The names of the columns for the virtual tuples. * @param rows An array of rows, where each row is Node[]. All rows should be * the same width, which is equal to columnNames.length. */ public TestContext(String[] columnNames, Node[][] rows) { this.columnNames = Arrays.asList(columnNames); this.rows = rows; mapGlobalizer(rows); } /** * Reset the position to the start. */ public void beforeFirst() { rowNumber = -1; } /** * Move to the next row of data. * @return <code>true</code> if the new row contains data, or <code>false</code> * if this object has moved past the last row. */ public boolean next() { return ++rowNumber < rows.length; } /**@see org.mulgara.query.filter.Context#getColumnValue(int) */ public long getColumnValue(int columnNumber) throws QueryException { if (columnNumber >= columnNames.size()) throw new QueryException("Unexpected column: " + columnNumber); Node v = rows[rowNumber][columnNumber]; if (v == null) throw new QueryException("Unbound column: " + columnNumber); return localizer.get(v); } /** @see org.mulgara.query.filter.Context#getInternalColumnIndex(java.lang.String) */ public int getInternalColumnIndex(String name) { return columnNames.contains(name) ? columnNames.indexOf(name) : NOT_BOUND; } /** @see org.mulgara.query.filter.Context#globalize(long) */ public Node globalize(long node) throws QueryException { Node n = globalizer.get(node); if (n == null) throw new QueryException("Unable to globalize id <" + node + ">"); return n != Null.NULL ? n : null; } /** @see org.mulgara.query.filter.Context#localize(org.jrdf.graph.Node) */ public long localize(Node node) throws QueryException { Long l = localizer.get(node); if (l == null) throw new QueryException("Unable to localize id <" + node + ">"); return l.longValue(); } /** @see org.mulgara.query.filter.Context#isBound(int) */ public boolean isBound(int columnNumber) throws QueryException { if (columnNumber >= columnNames.size()) throw new QueryException("Unexpected column: " + columnNumber); if (rowNumber < 0) throw new QueryException("beforeFirst() called on Context without next()"); if (rowNumber >= rows.length) throw new QueryException("called next() on Context too often"); return rows[rowNumber][columnNumber] != null; } /** @see org.mulgara.query.filter.Context#getUnboundVal() */ public long getUnboundVal() { return 0; } public boolean equals(Object o) { if (!(o instanceof TestContext)) return false; TestContext c = (TestContext)o; return c == this || eq(columnNames, c.columnNames) && Arrays.deepEquals(rows, c.rows) && eq(globalizer, c.globalizer); } /** * Gets a previously unused node ID. * @return a new Node ID. */ private long newNodeId() { return lastNode++; } /** * Map node IDs to the nodes, and nodes back to their IDs. * @param rows An array of node arrays. */ private void mapGlobalizer(Node[][] rows) { for (Node[] row: rows) { assert row.length == columnNames.size(); for (Node v: row) { if (v == null) v = Null.NULL; Long storedId = localizer.get(v); if (storedId == null) { storedId = newNodeId(); globalizer.put(storedId, v); localizer.put(v, storedId); } else { assert globalizer.get(storedId).equals(v) : "Bidirectional mapping for nodes<->ID failed"; } } } } /** Testing class used for storing a symbol for <code>null</code> that is disambiguated from a missing value */ @SuppressWarnings("serial") private static class Null implements Node { public static final Null NULL = new Null(); public int hashCode() { return -1; } public String stringValue() { return "null"; } public boolean isBlankNode() { return false; } public boolean isLiteral() { return false; } public boolean isURIReference() { return false; } } }