Sie sind auf Seite 1von 14

Core Java Technologies Tech Tips

Tips, Techniques, and Sample Code


Welcome to the Core Java Technologies Tech Tips for September 9,
2003. Here you'll get tips on using core Java technologies and
APIs, such as those in Java 2 Platform, Standard Edition (J2SE).
This issue covers:
* Working with SocketChannels
* Understanding AffineTransform
These tips were developed using Java 2 SDK, Standard Edition,
v 1.4.
This issue of the Core Java Technologies Tech Tips is written by
Daniel H. Steinberg, Director of Java Offerings for Dim Sum
Thinking, Inc, and editor-in-chief for java.net (http://java.net).
You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2003/tt0909.html.
See the Subscribe/Unsubscribe note at the end of this newsletter
to subscribe to Tech Tips that focus on technologies and products
in other Java platforms.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WORKING WITH SOCKETCHANNELS
The next time you have a networking task that has you reaching
for the Web Services API or some other high level API, consider
whether you can accomplish the same task more simply and with
less overhead using Sockets. In this Tech Tip you will use the
SocketChannel and ServerSocketChannel classes in the java.nio
package to create a simple networked application.
When you use a browser to view a particular web page, the details
are hidden from you. Open a browser and type in:
http://developer.java.sun.com/developer/JDCTechTips/
As a result, this entry connects you to port 80 of the web site.
It also sends a particular HTTP request that contains the name of
the page you want to view.
Now let's take these actions explicitly with a SocketChannel. The
following program, TechTipReader, uses a SocketChannel to
retrieve the page. Notice the getTips() method in the program.
The method first instantiates a SocketChannel. It then uses the
instance to connect to port 80 of developer.java.sun.com. Then
the method sends a standard HTTP "GET" request to retrieve the
JDCTechTips page.
The response from the server is printed to standard out.
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

import java.nio.ByteBuffer;
import java.net.InetSocketAddress;
import java.io.IOException;
public class TechTipReader {
private Charset charset =
Charset.forName("UTF-8");
private SocketChannel channel;
public void getTips() {
try {
connect();
sendRequest();
readResponse();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException e) {}
}
}
}
private void connect() throws IOException {
InetSocketAddress socketAddress =
new InetSocketAddress(
"developer.java.sun.com", 80);
channel = SocketChannel.open(socketAddress);
}
private void sendRequest() throws IOException {
channel.write(charset.encode("GET "
+ "/developer/JDCTechTips/"
+ "\r\n\r\n"));
}
private void readResponse() throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while ((channel.read(buffer)) != -1) {
buffer.flip();
System.out.println(charset.decode(buffer));
buffer.clear();
}
}
public static void main(String[] args) {
new TechTipReader().getTips();
}
}
Looking further into the TechTipReader program, notice that the
connect() method creates an InetSocketAddress by passing in two
parameters: a String that represents the domain, and the standard
HTTP port 80. The call to the static method SocketChannel.open()
takes the InetSocketAddress as a parameter. That call is
equivalent to the following two steps:

channel = SocketChannel.open();
channel.connect(socketAddress);
The sendRequest() method sends the three-line request. The first
line is "GET /developer/JDCTechTips/" and the second and third
lines are empty. The call to channel.write() takes a ByteBuffer
as its argument so you have to transform the String. Much of the
time in working with SocketChannels involves converting to and
from ByteBuffers. There are many ways to do the conversion, this
example uses the encode() method of the java.nio.charset.Charset
class.
The readResponse() method is responsible for handling the
response that is returned to the SocketChannel. The method reads
from the SocketChannel into a ByteBuffer. As long as it hasn't
reached the end of the file being transferred, the method flips
the buffer from reading to being ready to write. The contents of
the ByteBuffer are transformed by the charset.decode() method to
a String that is sent to standard out.
When you run the TechTipReader program, you should see the HTML
for the page displayed.
Now let's look at a second example. In this example, two numbers
are added. The example involves a client and a server. When the
client has two numbers to add, it sends the numbers to a server.
The server then performs the addition and returns the sum. This
example is based on the SocketChannel API. Other possible
solutions could use Web services, RMI, or servlets.
In the example, the contents of the ByteBuffer are two ints. This
allows you to restrict the ByteBuffer to eight bytes. It also
allows you to create an IntBuffer that serves as a view into the
ByteBuffer, as follows:
private ByteBuffer buffer = ByteBuffer.allocate(8);
private IntBuffer intBuffer = buffer.asIntBuffer();
The following program, SumClient, provides the client part of
the example. In order to run SumClient, you first have start up
the server part of the example, SumServer, which you can find
later in this tip.
import
import
import
import
import

java.nio.channels.SocketChannel;
java.nio.ByteBuffer;
java.nio.IntBuffer;
java.io.IOException;
java.net.InetSocketAddress;

public class SumClient {


private SocketChannel channel;
private ByteBuffer buffer = ByteBuffer.allocate(8);
private IntBuffer intBuffer = buffer.asIntBuffer();
public void getSum(int i, int j) {
try {
channel = connect();
sendSumRequest(i, j);
receiveResponse();

} catch (IOException e) {
// add exception handling code here
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
// add exception handling code here
e.printStackTrace();
}
}
}
}
private SocketChannel connect()
throws IOException {
InetSocketAddress socketAddress =
new InetSocketAddress("localhost", 9099);
return SocketChannel.open(socketAddress);
}
private void sendSumRequest(int i, int j)
throws IOException {
buffer.clear();
intBuffer.put(0, i);
intBuffer.put(1, j);
channel.write(buffer);
System.out.println("Sent request for sum of "
+ i + " and " + j + "...");
}
private void receiveResponse()
throws IOException {
buffer.clear();
channel.read(buffer);
System.out.println(
"Received response that sum is "
+ intBuffer.get(0) + ".");
}
public static void main(String[] args) {
new SumClient().getSum(14, 23);
}
}
The getSum() method in SumClient connects a SocketChannel to
a predetermined address. The connect() method is essentially the
same as the one in the TechTipReader example. After calling
connect(), the getSum() method passes two ints (in the example,
14 and 23) to the sendSumRequest() method, which, in turn, loads
the ByteBuffer and writes the contents to the SocketChannel. The
receiveResponse() method then takes the contents of the returned
method and writes them to standard out.
When you run SumClient (after starting SumServer), it should
display the following:
Sent request for sum of 14 and 23...
Received response that sum is 37.

Looking further at SumClient, notice that sendSumRequest() uses


two different views of the same ByteBuffer. First, buffer.clear()
discards the mark in the buffer, sets the position back to zero,
and sets the limit back to its capacity. In effect it clears the
buffer. Next, the ByteBuffer is viewed as a buffer that holds two
ints using the intBuffer handle. The int passed in as the first
parameter to sendSumRequest() is put in the first slot using
intBuffer.put(0,i), and the second parameter is similarly put in
the second slot. The write() method of SocketChannel takes a
ByteBuffer, so the first view of the buffer is required.
The receiveResponse() method parallels sendSumRequest(). The
buffer is again cleared. Then the contents of the SocketChannel
are read into the ByteBuffer view of the buffer. Next, the
IntBuffer is used to return the int in the first position.
Before moving on to the SumServer, the server part of the
solution, you might want to put an additional safeguard into the
places where you are calling the read() and write() methods on
the SocketChannel object. These methods return a long with the
number of bytes written or read. In this case you are expecting
eight bytes. Find the following line inside of sendSumRequest():
channel.write(buffer);
Replace it with this check on the size of the buffer being
written.
if (channel.write(buffer)!= 8){
throw new IOException("Expected 8 bytes.");
}
Put a similar guard around the channel.read() call in the
receiveResponse() method.
Here's SumServer. To make the example work, you need to set up
a server that listens for incoming requests in an agreed upon
location. In the openChannel() method below the port 9099 is used.
A call is made to the static method open() in ServerSocketChannel,
the next line binds the socket to the specified port. As soon as
channel.isOpen() returns true, a confirmation message is sent to
standard out.
import
import
import
import
import
import

java.nio.ByteBuffer;
java.nio.IntBuffer;
java.nio.channels.ServerSocketChannel;
java.nio.channels.SocketChannel;
java.io.IOException;
java.net.InetSocketAddress;

public class SumServer {


ByteBuffer buffer = ByteBuffer.allocate(8);
IntBuffer intBuffer = buffer.asIntBuffer();
ServerSocketChannel channel = null;
SocketChannel sc = null;
public void start() {
try {

openChannel();
waitForConnection();
} catch (IOException e) {
e.printStackTrace();
}
}
private void openChannel() throws IOException {
channel = ServerSocketChannel.open();
channel.socket().bind(
new InetSocketAddress(9099));
while (!channel.isOpen()) {
}
System.out.println("Channel is open...");
}
private void waitForConnection()
throws IOException {
while (true) {
sc = channel.accept();
if (sc != null) {
System.out.println(
"A connection is added...");
processRequest();
sc.close();
}
}
}
private void processRequest() throws IOException {
buffer.clear();
sc.read(buffer);
int i = intBuffer.get(0);
int j = intBuffer.get(1);
buffer.flip();
System.out.println("Received request to add "
+ i + " and " + j);
buffer.clear();
intBuffer.put(0, i + j);
sc.write(buffer);
System.out.println("Returned sum of " +
intBuffer.get(0) + ".");
}
public static void main(String[] args) {
new SumServer().start();
}
}
When you start SumServer, it should display the following:
Channel is open...
Sent request for sum of 14 and 23...
Received response that sum is 37.
Then, when you run SumClient, SumServer should display the
following:

A connection is added...
Received request to add 14 and 23
Returned sum of 37.
In the waitForConnection() method, the application cycles until
a request to connect to the ServerSocketChannel arrives. When a
request does arrive, channel.accept() returns a SocketChannel, so
the variable sc is no longer null. The incoming request is
processed and then the SocketChannel is closed. The server then
awaits another connection request. Note that in this simple
example requests are processed fully one at a time. A different
structure would be required for a more sophisticated server that
handles many concurrent requests but that is beyond the scope of
this tip.
The processRequest() method is much like the corresponding
methods in the SumClient. The buffer is cleared, the channel is
read into the buffer. Then the IntBuffer view is used to retrieve
the two ints from the buffer. The buffer is then cleared and the
sum is loaded back in using the IntBuffer view. The buffer is
then written back to the SocketChannel.
For more information about SocketChannels, see the technical
article "New I/0 Functionality for Java 2 Standard Edition 1.4"
(http://java.sun.com/jdc/technicalArticles/releases/nio/).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - UNDERSTANDING AFFINETRANSFORM
Included in the Java 2D classes is the class
java.awt.geom.AffineTransform which allows you to perform affine
transformations. An affine transformation transforms the
coordinates of a two-dimensional image in such a way that
parallel lines remain parallel. For example, if you start with
a rectangle, an affine transformation might produce a
parallelogram in a different location, but lines remain lines and
parallelism is preserved. Affine transformations can be
translations, rotations, scaling (which includes flips), and
shears.
The documentation for the AffineTransform class explains that:
Such a coordinate transformation can be represented by a 3 row
by 3 column matrix with implied last row of [0 0 1]. This
matrix transforms source coordinates (x,y) into destination
coordinates (x',y') by considering them to be a column vector
and multiplying the coordinate vector by the matrix according
to the following process:
[ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ]
[ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ]
[ 1 ] [ 0
0
1 ] [ 1 ] [
1
]
This Tech Tip explains why you need a 3 by 3 matrix to express
the affine transformations of two-dimensional space. It relates
the matrices for the fundamental transformations to the method
calls in the AffineTransform class.
In this tip, you'll run the following program, AffineChecker, to
compare AffineTransform transformations to matrix multiplications.

Notice the three instance variables of type Point2D.Double in the


program. These correspond to a starting point that has coordinate
(2,3), and the points that result from each of the
transformations, respectively.
import java.awt.geom.Point2D;
import java.awt.geom.AffineTransform;
public class AffineChecker {
private static Point2D startingPoint
= new Point2D.Double(2,3);
private static Point2D affineResult
= new Point2D.Double();
private static Point2D matrixResult
= new Point2D.Double();
public static void matrixMultiply(
double m00, double m01,
double m10, double m11){
double x = m00 * startingPoint.getX() +
m01 * startingPoint.getY();
double y = m10 * startingPoint.getX() +
m11 * startingPoint.getY();
matrixResult.setLocation(x,y);
}
public static void perform(AffineTransform at){
at.transform(startingPoint,affineResult);
}
public static void report(){
System.out.println("Affine Result = ("
+ affineResult.getX() + " , "
+ affineResult.getY() + ")");
System.out.println("Matrix Result = ("
+ matrixResult.getX() + " , "
+ matrixResult.getY() + ")");
if (affineResult.distance(matrixResult)<.0001){
System.out.println("
No real difference");
}
}
}
The matrixMultiply() method in AffineChecker provides the
standard matrix multiplication:
[ m00 m01 ] [ x ] [ m00x + m01y]
[ m10 m11 ] [ y ] = [ m10x + m11y]
The report() method in the program prints the results for the
transformation and the matrix multiplication, and indicates
whether the results are close enough to be essentially the same.
As a first test, let's do nothing to the starting point and then
make comparisons. To do this, run the following program:
import java.awt.geom.AffineTransform;
public class NothingChecker {
public static void main(String[] args){

System.out.println(
"The result of doing nothing:");
AffineChecker.perform(
AffineTransform.getRotateInstance(0));
AffineChecker.matrixMultiply(1,0,
0,1);
AffineChecker.report();
}
}
NothingChecker creates a new rotation transformation and passes
in the degrees to be rotated (in this case, 0) using the static
method call:
AffineTransform.getRotateInstance(0)
NothingChecker then calls the transform() method (through
AffineChecker's perform() method) on the returned AffineTransform.
Note that the transform() method is passed the startingPoint and
affineResult to transform the point (2,3) and store the resulting
point in affineResult. Next, NothingChecker compares the Affine
Result with the result of multiplying by the 2 by 2 identity
matrix:
[ 1 0 ]
[ 0 1 ]
When you run NothingChecker, you should see the following:
The result of doing nothing:
Affine Result = (2.0 , 3.0)
Matrix Result = (2.0 , 3.0)
No real difference
In other words, you should find that the result of both
transforms is the point (2,3).
You can scale in the x and/or y directions using the
getScaleInstance() static method of AffineTransform. Try to scale
the x direction by a factor of four and the y direction by a
factor of five. Here is the matrix multiplication that corresponds
to this scaling.
[ 4 0 ] [ x ] [ 4x ]
[ 0 5 ] [ y ] = [ 5y ].
The following program shows the call to getScaleInstance(). You
can run the program to compare the transformation to the matrix
multiplication:
import java.awt.geom.AffineTransform;
public class Scale4Xand5YChecker {
public static void main(String[] args) {
System.out.println(
"The results of scaling four " +
"times in the x direction " +
"and five times in the y:");
AffineChecker.perform(
AffineTransform.getScaleInstance(4,5));

AffineChecker.matrixMultiply(4,0,
0,5);
AffineChecker.report();
}
}
The results should look like this:
The results of scaling four times in the x direction and five
times in the y:
Affine Result = (8.0 , 15.0)
Matrix Result = (8.0 , 15.0)
No real difference
Again, the results are the same.
You can reflect about the x and/or y axes by entering a negative
number into one of the scaling coefficients. For example,
[ -1 0 ] [ x ] [ -x ]
[ 0 1 ] [ y ] = [ y ]
is a reflection in the x direction (reflecting about the y axis).
Here is a program that performs the reflection and compares it to
the matrix multiplication:
import java.awt.geom.AffineTransform;
public class FlipChecker {
public static void main(String[] args) {
System.out.println(
"The results of flipping over" +
"the y-axis:");
AffineChecker.perform(
AffineTransform.getScaleInstance(-1,1));
AffineChecker.matrixMultiply(-1, 0,
0, 1);
AffineChecker.report();
}
}
and the results are:
The results of flipping over the y-axis:
Affine Result = (-2.0 , 3.0)
Matrix Result = (-2.0 , 3.0)
No real difference
A rotation expects its argument to be given in radians and not
degrees. As a reminder, you can easily convert from degrees to
radians by multiplying by pi and dividing by 180 degrees. For
example, thirty degrees corresponds to pi/6. To rotate through
pi/6 radians you multiply by the rotation matrix:
[ cos pi/6 -sin pi/6 ]
[ sin pi/6 cos pi/6 ]
You might recall that one characteristic of rotation matrices is
that their determinant is 1. In code, you check the results like
this:

import java.awt.geom.AffineTransform;
public class RotateThirtyChecker {
public static void main(String[] args) {
System.out.println(
"The results of a thirty degree"
+ " counter clockwise rotation:");
AffineChecker.perform(
AffineTransform.getRotateInstance(
Math.PI/6));
AffineChecker.matrixMultiply(
Math.cos(Math.PI/6), -Math.sin(Math.PI/6),
Math.sin(Math.PI/6), Math.cos(Math.PI/6));
AffineChecker.report();
}
}
Run RotateThirtyChecker and get:
The results of a thirty degree counter clockwise
rotation:
Affine Result =
(0.23205080756887764 , 3.598076211353316)
Matrix Result =
(0.23205080756887764 , 3.598076211353316)
No real difference
A shearing transform changes the shape of the transformed object.
The coefficients shx and shy indicate how much the x direction is
transformed as a factor of the y coordinate, and how much the y
direction is transformed as a factor of the x coordinate. This is
the transformation that turns a rectangle into a parallelogram.
The matrix representation is
[ 1 shx ] [ x ] [ x + y (shx) ]
[ shy 1 ] [ y ] = [ y + x (shy) ].
The code to do the transform and compare it to the matrix
multiplication is this:
import java.awt.geom.AffineTransform;
public class ShearChecker {
public static void main(String[] args) {
System.out.println(
"The results of shearing:");
AffineChecker.perform(
AffineTransform.getShearInstance(5,6));
AffineChecker.matrixMultiply(1, 5,
6, 1);
AffineChecker.report();
}
}
Run ShearChecker and get:
The results of shearing:
Affine Result = (17.0 , 15.0)
Matrix Result = (17.0 , 15.0)

No real difference
What remains is, in some sense, the easiest transform. It's a
translation that just moves the shape over by some fixed amount.
To move tx in the x direction and ty in the y direction you
perform the following addition.
[ tx ] [ x ] [ tx + x ]
[ ty ] + [ y ] = [ ty + y ].
The problem is that until now, every transformation has been
expressable in terms of multiplication with a 2 by 2 matrix. One
way to combine these ideas is that all transformations can be
written using the following.
[ m00 m01 ] [ x ] [ tx ] [ m00x + m01y + tx ]
[ m10 m11 ] [ y ] + [ ty ] = [ m10x + m11y + ty ]
You can write the left side more simply if you add a third row
like this.
[ m00 m01 tx ] [ x ] [ m00x + m01y + tx ]
[ m10 m11 ty ] [ y ] = [ m10x + m11y + ty ]
[ 0
0
1 ] [ 1 ] [
1
]
Now all of these building block transformations can be written in
terms of the six coefficients m00, m01, m10, m11, tx, and ty.
Note that the order matters. This corresponds to applying the top
left 2 by 2 matrix and then performing the translation. Add these
two methods to AffineChecker.
private static void translate(double tx, double ty){
matrixResult.setLocation(
matrixResult.getX() + tx,
matrixResult.getY() + ty);
}
public static void matrixTransform(
double m00, double m01,
double m10, double m11,
double tx, double ty){
matrixMultiply(m00,m01,m10,m11);
translate(tx,ty);
}
You can now call any of the basic transformations using the
matrixTransform() method. For example, you can replace the call
in ShearChecker to matrixMultiply(1,5,6,1) with a call to the
new method matrixTransform(1,5,6,1,0,0). A translation can also
be called using the matrixTransform() method as is shown in the
following program:
import java.awt.geom.AffineTransform;
public class TranslationChecker {
public static void main(String[] args) {
System.out.println("The results of translating " +
"by (-2,5):");
AffineChecker.perform(
AffineTransform.getTranslateInstance(-2,5));

AffineChecker.matrixTransform(1,0,0,1,-2,5);
AffineChecker.report();
}
}
Run TranslationChecker (remember to first update AffineChecker
with the two additional methods) and get:
The results of translating by (-2,5):
Affine Result = (0.0 , 8.0)
Matrix Result = (0.0 , 8.0)
No real difference
These basic operations can be combined to get sophisticated
results many of which are captured in convenience methods in the
AffineTransform class.
For more information about AffineTransform, see "Transforming
Shapes, Text, and Images"
(http://java.sun.com/docs/books/tutorial/2d/display/transforming.html)
in the Java Tutorial.
. . . . . . . . . . . . . . . . . . . . . . .
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing
policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
* FEEDBACK
Comments? Please enter your feedback on the Tech Tips at:
http://developers.sun.com/contact/feedback.jsp?category=sdn
* SUBSCRIBE/UNSUBSCRIBE
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using
enterprise Java technologies and APIs, such as those in the
Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless
Java technologies and APIs, such as those in the Java 2
Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page,
(http://developer.java.sun.com/subscription/),
choose the newsletters you want to subscribe to and click
"Update".
- To unsubscribe, go to the subscriptions page,
(http://developer.java.sun.com/subscription/),
uncheck the appropriate checkbox, and click "Update".
- To use our one-click unsubscribe facility, see the link at
the end of this email:
- ARCHIVES
You'll find the Core Java Technologies Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html
- COPYRIGHT
Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, California 95054 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
Core Java Technologies Tech Tips
September 9, 2003
Trademark Information: http://www.sun.com/suntrademarks/
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks
or registered trademarks of Sun Microsystems, Inc. in the
United States and other countries.

Das könnte Ihnen auch gefallen