The RowListStarTable
described in the previous section
is adequate for many table processing purposes, but since it controls
how storage is done (in a List
of rows) it imposes a
number of restrictions - an obvious one is that all the data have
to fit in memory at once.
A number of other classes are provided for more flexible table handling,
which make heavy use of the "pull-model" of processing, in which
the work of turning one table to another is not done at the time
such a transformation is specified, but only when the transformed table data
are actually required, for instance to write out to disk as a new
table file or to display in a GUI component such as a JTable
.
One big advantage of this is that calculations which are never used
never need to be done. Another is that in many cases it means you
can process large tables without having to allocate large amounts of
memory. For multi-step processes, it is also often faster.
The central idea to get used to is that of a "wrapper" table.
This is a table which wraps itself round another one (its "base" table),
using calls to the base table to provide the basic data/metadata
but making some some modifications before it returns it to the caller.
Tables can be wrapped around each other many layers deep like an onion.
This is rather like the way that
java.io.FilterInputStream
s
and to some extent
java.util.stream.Stream
s
work.
Although they don't have to, most wrapper table classes inherit
from WrapperStarTable
.
This is a no-op wrapper, which simply delegates all its calls to
the base table.
Its subclasses generally leave most of the methods alone, but
override those which relate to the behaviour they want to change.
Here is an example of a very simple wrapper table, which simply
capitalizes its base table's name:
class CapitalizeStarTable extends WrapperStarTable { public CapitalizeStarTable( StarTable baseTable ) { super( baseTable ); } public String getName() { return getBaseTable().getName().toUpperCase(); } }As you can see, this has a constructor which passes the base table to the
WrapperStarTable
constructor itself, which takes the base table as an argument.
Wrapper tables which do any meaningful wrapping will have
a constructor which takes a table, though they may take additional
arguments as well.
More often it is the data part which is modified and the metadata which is
left the same - some examples of this are given in Section 6.4.
Some wrapper tables wrap more than one table,
for instance joining two base tables
to produce a third one which draws data and/or metadata from both
(e.g. ConcatStarTable
,
JoinStarTable
).
The idea of wrappers is used on some components other than
StarTable
s themselves: there are
WrapperRowSequence
s and
WrapperColumn
s as well.
These can be useful in implementing wrapper tables.
Working with wrappers can often be more efficient than,
for instance, doing a calculation
which goes through all the rows of a table calculating new values
and storing them in a RowListStarTable
.
If you familiarise yourself with the set of wrapper tables supplied
by STIL, hopefully you will often find there are ones there which
you can use or adapt to do much of the work for you.