/**
* Copyright (c) 2011-2014 Exxeleron GmbH
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.exxeleron.qjava;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Represents a q table type.
*/
public final class QTable implements Iterable<QTable.Row>, Table {
private final String[] columns;
private final Object[] data;
private final int rowsCount;
private final Map<String, Integer> columnsMap;
/**
* Initializes a new instance of the {@link QTable} with specified column names and data matrix.
*
* @param columns
* column names
* @param data
* data matrix
*
* @throws IllegalArgumentException
*/
public QTable(final String[] columns, final Object[] data) {
if ( columns == null || columns.length == 0 ) {
throw new IllegalArgumentException("Columns array cannot be null or 0-length");
}
if ( data == null || data.length == 0 ) {
throw new IllegalArgumentException("Data matrix cannot be null or 0-length");
}
if ( columns.length != data.length ) {
throw new IllegalArgumentException("Columns array and data matrix cannot have different length");
}
for ( final Object col : data ) {
if ( col == null || !col.getClass().isArray() ) {
throw new IllegalArgumentException("Non array column found in data matrix");
}
}
this.columnsMap = new HashMap<String, Integer>();
for ( int i = 0; i < columns.length; i++ ) {
this.columnsMap.put(columns[i], i);
}
this.columns = columns;
this.data = data;
this.rowsCount = Array.getLength(data[0]);
}
/*
* (non-Javadoc)
*
* @see com.exxeleron.qjava.Table#size()
*/
public int getRowsCount() {
return rowsCount;
}
/*
* (non-Javadoc)
*
* @see com.exxeleron.qjava.Table#getColumnsCount()
*/
public int getColumnsCount() {
return columns.length;
}
/*
* (non-Javadoc)
*
* @see com.exxeleron.qjava.Table#getColumnIndex(java.lang.String)
*/
public int getColumnIndex( final String column ) {
return columnsMap.get(column);
}
/*
* (non-Javadoc)
*
* @see com.exxeleron.qjava.Table#hasColumn(java.lang.String)
*/
public boolean hasColumn( final String column ) {
return columnsMap.containsKey(column);
}
/**
* Gets an array of columns in current {@link QTable}.
*
* @return an array of columns
*/
public String[] getColumns() {
return columns;
}
/**
* Gets a data matrix in current {@link QTable}.
*
* @return an array of arrays with internal representation of data
*/
public Object[] getData() {
return data;
}
/**
* Gets a row of data from current {@link QTable}.
*
* @param index
* 0 based row index
* @return Row object representing a row in current {@link QTable}
*/
public Row get( final int index ) {
return new Row(index);
}
/**
* Returns a String that represents the current {@link QTable}.
*
* @return a String representation of the {@link QTable}
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "QTable: " + Utils.arrayToString(columns) + "!" + Utils.arrayToString(data);
}
/**
* Indicates whether some other object is "equal to" this table. {@link QTable} objects are considered equal if the
* columns and data matrix are equal for both instances.
*
* @return <code>true</code> if this object is the same as the obj argument, <code>false</code> otherwise.
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( final Object obj ) {
if ( this == obj ) {
return true;
}
if ( !(obj instanceof QTable) ) {
return false;
}
final QTable t = (QTable) obj;
return Utils.deepArraysEquals(columns, t.columns) && Utils.deepArraysEquals(data, t.data);
}
/**
* Returns a hash code value for this {@link QTable}.
*
* @return a hash code value for this object
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return 31 * Utils.arrayHashCode(columns) + Utils.arrayHashCode(data);
}
/**
* <p>
* Returns an iterator over rows stored in the table. Iterator uses same {@link Row} instance as a moving window
* over the table data.
* </p>
*
* <p>
* Note that the iterator returned by this method will throw an {@link UnsupportedOperationException} in response to
* its <code>remove</code> method.
* </p>
*
* @see java.lang.Iterable#iterator()
*/
public Iterator<Row> iterator() {
return new Iterator<QTable.Row>() {
private int index = 0;
private Row row = new Row(index);
public boolean hasNext() {
return index < rowsCount;
}
public Row next() {
if ( hasNext() ) {
row.setRowIndex(index++);
return row;
} else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Represents view at single row in a table.
*/
public class Row implements Iterable<Object> {
private int rowIndex;
/**
* Creates row view for row with given index.
*
* @param rowIndex
* index of the row
*/
Row(final int rowIndex) {
setRowIndex(rowIndex);
}
/**
* Returns index of the row.
*
* @return {@link int}
*/
public int getRowIndex() {
return rowIndex;
}
/**
* Moves the row view to new index.
*
* @param rowIndex
* the rowIndex to set
*/
public void setRowIndex( final int rowIndex ) {
if ( rowIndex < 0 || rowIndex > rowsCount ) {
throw new IndexOutOfBoundsException();
}
this.rowIndex = rowIndex;
}
/**
* Creates a copy of entire row and returns it as an {@link Object} array.
*
* @return {@link Object}[] with copy of entire row
*/
public Object[] toArray() {
final int length = getLength();
final Object[] row = new Object[length];
for ( int i = 0; i < length; i++ ) {
row[i] = get(i);
}
return row;
}
/**
* Returns number of columns in the current {@link QTable}.
*
* @return number of columns
*/
public int getLength() {
return columns.length;
}
/**
* Gets an object stored under specific index.
*
* @param index
* 0 based index
* @return object
*/
public Object get( final int index ) {
return Array.get(data[index], rowIndex);
}
/**
* Sets an object stored under specific index.
*
* @param index
* 0 based index
* @param value
* value to be set
*/
public void set( final int index, final Object value ) {
Array.set(data[index], rowIndex, value);
}
@Override
public String toString() {
return Utils.arrayToString(columns) + "!" + Utils.arrayToString(toArray());
}
/**
* <p>
* Returns an iterator over columns in a particular row in the table.
* </p>
*
* <p>
* Note that the iterator returned by this method will throw an {@link UnsupportedOperationException} in
* response to its <code>remove</code> method.
* </p>
*
* @see java.lang.Iterable#iterator()
*/
public Iterator<Object> iterator() {
return new Iterator<Object>() {
int index = 0;
public boolean hasNext() {
return index < getLength();
}
public Object next() {
if ( hasNext() ) {
return Array.get(data[index++], rowIndex);
} else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
}