blob: d8ffef8b1b8baefa04cc90dbcac173354b204e7d [file] [log] [blame]
//////////////////////
* Copyright (c) 2007-2012, Niclas Hedhman. All Rights Reserved.
*
* 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.
//////////////////////
[[core-io,I/O API]]
= Core I/O API =
[devstatus]
--------------
source=core/io/dev-status.xml
--------------
The Zest™ Core I/O API is completely generic and not tied to the Zest™ programming model as a whole. It can be used
independently of Zest, together with the Zest™ Core Functional API, which the Core I/O API depends on.
The Zest™ Core I/O API tries to address the problem around shuffling data around from various I/O inputs and outputs,
possibly with transformations and filtering along the way. It was identified that there is a general mix-up of concerns
in the stereotypical I/O handling codebases that people deal with all the time. The reasoning around this, can be found
in the <<howto-use-io>>, and is recommended reading.
include::../../build/docs/buildinfo/artifact.txt[]
== The Problem ==
Why does I/O operations in Java have to be so complicated, with nested try/catch/finally and loops? Don't you wish
that the operations could be expressed in a more natural way, such as;
[source,java]
-------------
File source = ...
File destination = ...
source.copyTo( destination );
-------------
It seems natural to do, yet it is not present for us. We need to involve FileInputStream/FileOutputStream, wrap them
in Buffered versions of it, do our own looping, close the streams afterwards and what not. So, the java.io.File does
not have this simple feature and in the Zest™ Core API, we need to work around this limitation. We also want to
make the abstraction a little bit more encompassing than "just" files. So how does that look like then?
== First Examples ==
The most common inputs and outputs are collected in the org.qi4j.io.Inputs and org.qi4j.io.Outputs classes as static
factory methods, but you can create your (more about that later).
So, we want to read a text file and write the content into another text file, right? This is how it is done;
[snippet,java]
-----------
source=core/io/src/test/java/org/qi4j/io/docsupport/IoDocs.java
tag=io1
-----------
Pretty much self-explanatory, wouldn't you say? But what happened to the handling of exceptions and closing of
resources? It is all handled inside the Zest™ Core I/O API. There is nothing you can forget to do.
Another simple example, where we want to count the number of lines in the text;
[snippet,java]
-----------
source=core/io/src/test/java/org/qi4j/io/docsupport/IoDocs.java
tag=io2
-----------
The Counter is a <<core-functional>> which gets injected into the transfer.
== The 3 Parts ==
Ok, so we have seen that the end result can become pretty compelling. How does it work?
I/O is defined as a process of moving data from an Input, via one or more Transforms to an Output. The Input could
be a File or a String, the transformation could be a filter, conversion or a function and finally the Output
destination could be a File, String or an OutputStream. It is important to note that there is a strong separation of
concern between them. Let's look at the on at a time.
== org.qi4j.io.Input ==
This interface simply has a transferTo() method, which takes an Output. The formal definition is;
[snippet,java]
--------------
source=core/io/src/main/java/org/qi4j/io/Input.java
tag=input
--------------
What on earth is all this genericized Exceptions? Well, it abstracts away the explicit Exception that implementations
may throw (or not). So instead of demanding that all I/O must throw the java.io.IOException, as is the case in the
JDK classes, it is up to the implementation to declare what it may throw. That is found in the SenderThrowable generic
of the interface.
But hold on a second. Why is an Input throwing a "sender" exception? Well, think again. The Input is feeding "something"
with data. It takes it from some source and "sends it" to the downstream chain, eventually reaching an Output, which
likewise is the ultimate "receiver".
So, then, the method transferTo() contains the declaration of the downstream receiver's possible Exception
(ReceiverThrowable) which the transferTo() method may also throw as the data may not be accepted and such exception
will bubble up to the transferTo() method (the client's view of the transfer).
== org.qi4j.io.Output ==
The output interface is likewise fairly simple;
[snippet,java]
--------------
source=core/io/src/main/java/org/qi4j/io/Output.java
tag=output
--------------
It can simply receive data from a org.qi4j.io.Sender.
Hey, hold on! Why is it not receiving from an Input? Because the Input is the client's entry point and of no use to
the Output as such. Instead, the Output will tell the Sender (which is handed to from the Input or an transformation)
to which Receiver the content should be sent to.
Complicated? Perhaps not trivial to get your head around it at first, but the chain is;
Input passes a Sender to the Output which tells the Sender which Receiver the data should be sent to.
The reason for this is that we need to separate the concerns properly. Input is a starting point, but it emits something
which is represented by the Sender. Likewise the destination is an Output, but it receives the data via its Receiver
interface. For O/S resources, they are handled purely inside the Input and Output implementations, where are the
Sender/Receiver are effectively dealing with the data itself.
== org.qi4j.io.Transforms ==
The 3 component in the Zest™ Core I/O API is the transformations that are possible. Interestingly enough, with the
above separation of concerns, we don't need an InputOutput type that can both receive and send data. Instead, we
simply need to prepare easy to use static factory methods, which are found in the org.qi4j.io.Transforms class. Again,
it is fairly straight forward to create your own Transforms if you need something not provided here.
The current transformations available are;
* filter - takes a Specification and only forwards data items conforming to the Specification.
* map - takes a org.qi4j.functional.Function to convert an item from one type to (potentially) another, and any
possible change along the way.
* filteredMap - is a combination of a filter and a map. If the Specification is satisfied, the map function is
applied, otherwise the item is passed through unaffected.
* lock - A wrapper which protects the Input or Output from simultaneous access. Not a transformation by itself,
but implemented in the same fashion.
There are also a couple of handy map functions available, such as
* Log
* ProgressLog
* Counter
* ByteBuffer2String
* Object2String
* String2Bytes
== Writing a Map Function? ==
Let us take a closer look at the implementation of a map function, namely Counter mentioned above and also used in
the section First Example above.
The implementation is very straight forward.
[snippet,java]
--------------
source=core/io/src/main/java/org/qi4j/io/Transforms.java
tag=counter
--------------
On each call to the map() method, increment the counter field. The client can then retrieve that value after the
transfer is complete, or in a separate thread to show progress.
Speaking of "progress", so how is the ProgressLog implemented? Glad you asked;
[snippet,java]
--------------
source=core/io/src/main/java/org/qi4j/io/Transforms.java
tag=progress
--------------
It combines the Counter and the Log implementations, so that the count is forwarded to the Log at a given interval, such
as every 1000 items. This may not be what you think a ProgressLog should look like, but it serves as a good example on
how you can combine the general principles found in the Zest™ Core API package.
== How to write a filter specification? ==
The filter transform takes a specification implementation which has a very simple method, isSatisfiedBy() (read more
about that in <<core-functional>>.
[snippet,java]
--------------
source=core/functional/src/main/java/org/qi4j/functional/Specification.java
tag=specification
--------------
The only thing that the implementation need to do is return *true* or *false* for whether the item passed in is within
the limits of the Specification. Let's say that you have a IntegerRangeSpecification, which could then be implemented
as
[snippet,java]
--------------
source=core/functional/src/test/java/org/qi4j/functional/IntegerRangeSpecificationTest.java
tag=specification
--------------
== Ready-to-use components ==
Input and Output implementations at first glance look quite scary. Taking a closer look and it can be followed. But to
simplify for users, the org.qi4j.io.Inputs and org.qi4h.io.Outputs contains static factory methods for many useful
sources and destinations.
== org.qi4j.io.Inputs ==
The current set of ready-to-use Input implementations are;
[snippet,java]
--------------
source=core/io/src/main/java/org/qi4j/io/Inputs.java
tag=method
--------------
== org.qi4j.io.Outputs ==
The current set of ready-to-use Input implementations are;
[snippet,java]
--------------
source=core/io/src/main/java/org/qi4j/io/Outputs.java
tag=method
--------------