Wednesday, March 30, 2016

another java hacking story, part 3

OK, here's the source.

Note that it does not handle a variety of return codes. I'll work that later as I need them. And I'll come back to this to add things if I remember to.

Completely self-contained in this one file.

Two test cases are included, so you can see that "chunked" return and "normal" return are both handled.

package com.hu.commons;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;

/**
 * AAAAHHHH!
 *
 * This is a scratch rewrite of broken functionality elsewhere.
 *
 * the problem: java.net.HttpURLConnection works just fine when the return is standard, simple, "Content-length: N" where N>0
 *                         BUT IT DOES NOT WORK WITH CHUNKED RESULTS
 *
 * So I tried spring.framework.core.httpclient as an alternative. THAT DIDN'T WORK EITHER. Different, but similar problem about chunked.
 *
 * So I'm making my own replacement, from scratch.
 *
 * This will grow over time to handle more flavors of results. Right now, just 200, 204, 400, 404, 500.
 *
 * using ByteArrayOutputStream as byte accumulator for reading is much simpler.
 *
 * To use: make an instance of NanoHTTPClient. set debug=true if you want verbose output. Call sendMessageGet(url-string) and get a string back.
 *
 * test cases are at httpbin.org and typicode.com

  * for proof about the chunked part, use www.google.com
 *
 * @copyright 2016, Hyde University.
 *
 * feel free to use as you wish, but you may not claim to be the author.
 *
 * find something wrong? oh well.
 *
 */

public class NanoHTTPClient {

    // these really need to be local to instances.
    //probably shouldn't be public either.
    String protocol = "http";
    public String url = null;
    public String host = null;
    public int port = 80; // standard default
    public String method = null; // GET, POST, etc
    public String version = "HTTP/1.1";
    public String serverRev = null; // server's http version
    public String returnCode = null; // 200/400/500/etc
    public String returnMsg = null; // third part of the first line: "OK", "Bad Request" etc
   
    // the in/out header-block properties
    private Properties sendprops = new Properties();
    private Properties receiveprops = new Properties();
   
    boolean debug = false; // aka verbose
   
    // *******************************************************
    // *******************************************************

    public NanoHTTPClient() {}
   
    // *******************************************************
    // *******************************************************

    public void setRequestProperty(String prop, String value) {
        sendprops.setProperty(prop, value);
    }
   
    // *******************************************************
    // *******************************************************

    public String getReeiveProperty(String prop) {
        return receiveprops.getProperty(prop);
    }
   
    // *******************************************************
    // *******************************************************

    // ok, we can't necessarily trust reading characters here and bytes elsewhere
    // so its bytes everywhere
   
    // if your input contains embedded \r\n occurrences, well, this method won't know that, and will assume that's the end.
   
    // it's possible for this to screw up, too, if you input happens to contain a byte sequence
    // that can be interpreted as a valid multi-byte character. Had that happen once.
   
    public String readLine(InputStream is) {
        String result = "";
       
        //ok, we read until we get to the first line break (\r\n)
       
        // BAOS is an accumulator of bytes, you use it like a stream, but you can get a String of the content at any time.
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            while (true) {
                int b = is.read();
                if (b=='\r') {
                    b=is.read();
                    if (b=='\n') break;
                }
                else
                    baos.write(b);
                }
            } catch (Exception err) {
                err.printStackTrace();
        }
        //ok, make sure we get a clean conversion
        try {result = baos.toString("UTF-8");}
        catch (UnsupportedEncodingException err) {err.printStackTrace();}
       
        if (debug) System.out.println("RDL: " + result);
       
        return result;
    }
   
    // *******************************************************
    // *******************************************************

    // regular old GET call.
   
    // an important thing to understand here, part of the "why" of this code,
    //  is that you can't mix/match a wrapper stream on a base stream and expect
    // reading to behave properly
   
    // this is the problem with java.net.httpclient--there's a base stream in there,
    // httpclient itself reads from that stream, so that if you want to use it,
    // you can't put a wrapper on it (like BufferedReader), because you can't quite expect that the pointer
    // position on that stream is where you think it is after reading the http headers.
   
    // sometimes it isn't; if you mix stream readers, it definitely isn't.
    //  that probably has to do with how the O/S input buffer fits in.
    // imagine that the wrapper clears the i/o buffer when it starts.
    // the wrapper is then at zero, but the underlying stream is, well, *somewhere*
   
    public String callMethodGet (String url) {
        try {
            URL url1 = new URL(url); // this fails on standard reasons, basically the syntax format is wrong.
           
            System.out.println(url1);
           
            //if we happened to be reusing this instance, you want these cleared each time.
            sendprops.clear();
            receiveprops.clear();

            // hold these for later? am I using them yet?
            host = url1.getHost();
            if (url1.getPort()>0) port = url1.getPort(); // default properly
           
            // are these order dependent? nah, apparently not. good.
            sendprops.setProperty("User-Agent", "NanoHTTPClient"); // always use this name, no spoofing that. // or it might need to be that Mozilla/Gecko thing.
            if (sendprops.getProperty("Accept") == null) sendprops.setProperty("Accept", "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,*/*;q=0.5");
            if (sendprops.getProperty("Accept-Language") == null) sendprops.setProperty("Accept-Language", "en-us,ex;q=0.5");
            if (sendprops.getProperty("Accept-Charset") == null) sendprops.setProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
            // this is required, some tiny flavor of security check
            sendprops.setProperty("Host", host + ":" + port);
           
            // ok let's go
            Socket so = new Socket(host, port);
           
            // time to write all the proper stuff to the server
            OutputStream os = so.getOutputStream();
           
            byte[] b = null;
           
            String line = "GET " + url1.getFile() + " " + version + "\r\n"; // getFile includes the query params
            b = line.getBytes();
            os.write(b);
            os.flush(); // don't forget this, you have no idea what buffering is going on behind the scenes.
            if (debug) System.out.println(line);
           
            // do the real work
            // push all the "send" properties across. order not important.
            Enumeration props = (Enumeration)sendprops.propertyNames();
            while (props.hasMoreElements()) {
                String prop = props.nextElement();
                line = prop + ":" + sendprops.getProperty(prop) + "\r\n";
                if (debug) System.out.println(line);
                b = line.getBytes();
                os.write(b);
                os.flush();
            }
            // need an extra blank line
            os.write("\r\n".getBytes());
            os.flush();
           
            // ok that's all for sending to the server
           
            if (debug) System.out.println("-------------");
           
            // *********************************************
           
            // all done. ready to receive.
           
            InputStream is = so.getInputStream();
           
            // easier reading of headers. body is separate handling anyway.
            // no, this won't work right here with multi-byte characters. Plus, you can't mix streams.
            // BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            // I deleted the lines below that went with "br" type of stream.
           
            // rather than read bytes, let's read lines, since the header part is all guaranteed to be lines.
            // we'll read the entire header block up to the blank line separator.
           
            line = readLine(is);
           
            if (debug)
                    System.out.println("Ret Code: " + line);

            String[] split = line.split(" ");
           
            serverRev = split[0];
            returnCode = split[1];
            returnMsg = split[2];
           
            //now read headers. don't think too hard about this.
            while ((line= readLine(is)).length() >0) {
                if (debug) System.out.println(line);
                split = line.split(": ");
                receiveprops.setProperty(split[0], split[1]);
            }
            if (debug) System.out.println();
           
            // get the content-length, if specified. If not, default to -1, which we expect means "chunked"
            // the whole reason for this code in the first place.
           
            int len = Integer.parseInt(receiveprops.getProperty("Content-Length", "-1")); // returns -1 even if there's no entry at all
           
            if (debug) System.out.println("CL: " + len);
           
            String sb = null;//new String("");
           
            // this is the empty result
            if (returnCode.equals("204")) { // nothing to see here, folks. move along.               
            }
           
            // normal result
            else if (returnCode.equals("200")) {
                // all's well
                if (len>0) { // normal content, one long byte-sequence.
                    int retry = 0;
                    int num_tries = 5;
                   
                    byte[] bts = new byte[len];
                   
                    ByteArrayOutputStream baos = new ByteArrayOutputStream(len); // we know the size
                   
                    // ok, this might be excessive. for small results, it undoubtedly is. long ones, gotta do it.
                    while (retry++                        if (is.available()>0) {
                            int len2 = is.read(bts);
                            baos.write(bts, 0, len2);
                        } else {
                        // interesting. Adding the println wastes enough time that the socket catches up to the incoming data.
                        // otherwise, above loop goes too fast and terminates before all the data is read.
                        System.out.println("#");
                        }
                    }
                    sb = baos.toString("UTF-8");
                }
               
                // now for chunked encoding
                else if (receiveprops.containsKey("Transfer-Encoding")
                        && receiveprops.getProperty("Transfer-Encoding").equalsIgnoreCase("chunked")) {
                   
                    String chunkSizeLine = readLine(is); // this is a hex value
                   
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                   
                    // now we are reading the chunk
                    while (true) {
                        if (debug) System.out.println("chunk size line hex: " + chunkSizeLine);
                       
                        // next is chunk size, which is given in hex.
                        int chunkSize = Integer.parseInt(chunkSizeLine, 16);
                        if (chunkSize == 0) break; // final chunk size is always zero. This not zero? not final chunk.

                        if (debug) System.out.println("chunk size decimal: " + chunkSize);
                       
                        // in general, the chunks aren't large. Doesnt mean they can't be...
                        byte[] buf = new byte[chunkSize];
                        int rlen = 0;
                        // read the entire chunk.
                        while (rlen                            rlen += is.read(buf,rlen,chunkSize-rlen);
                        }
                        baos.write(buf);
                       
                        if (debug) System.out.println("bytes read (=chunk size decimal): " + rlen);
                       
                        // on to the next chunk
                        // read a blank
                        readLine(is);
                        chunkSizeLine= readLine(is);
                        if (debug) System.out.println();
                    }
                   
                    String temp = baos.toString("UTF-8");
                    if (debug) System.out.println(temp);
                   
                    sb = temp; // this is it
                }
            }
           
            // ok, I probably better read an error message
            // I think the server actually creates these errors, when your calling params don't match the method signature
            else if (returnCode.equals("400")
                    || returnCode.equals("404")
                    || returnCode.equals("500")
                    ) {
                if (debug) System.out.println(receiveprops);
               
                if (len>0)  { // normal content, one long byte-sequence. even though it's an error.
                    int retry = 0;
                    int num_tries = 5;
                   
                    byte[] bts = new byte[len];
                   
                    ByteArrayOutputStream baos = new ByteArrayOutputStream(len); // we know the size
                   
                    // ok, this might be excessive. for small results, it undoubtedly is. long ones, gotta do it.
                    while (retry++                        while (is.available()>0) {
                            int len2 = is.read(bts);
                            baos.write(bts, 0, len2);
                        }
                    }
                    sb = baos.toString("UTF-8");
                }
            }
            is.close();
            so.close();
           
            if (debug) System.out.println(sb.length());
           
            return sb;
           
        } catch (MalformedURLException err) {
            err.printStackTrace();
        } catch (UnknownHostException err) {
            err.printStackTrace();
        } catch (IOException err) {
            err.printStackTrace();
        }
        return null; // oops.
    }

    // *******************************************
    // *******************************************
    // *******************************************

    public static void main(String[] args) {
        NanoHTTPClient nano = new NanoHTTPClient();

        // C-L normal return.
        System.out.println(nano.callMethodGet("http://httpbin.org/"));
        System.out.println(nano.callMethodGet("http://httpbin.org/ip"));
       
        //nano.debug = true;
        // YAY! CHUNKED!
        System.out.println(nano.callMethodGet("http://www.google.com/"));
    }
   
}

another java hacking story, part 2

So do it myself means I open the socket, I write the output to the server, then I read the input from the server.

Means I have to write the "readLine" method. Again.

Well, anyway. Nothing I haven't done before. Except the business about "getErrorStream()" in HttpUrlConnection. What is that?

Took me a while to hunt down the right thing, which is the deeply buried Sun code for this class. What happens: normal process is for the header block to be read for you, then you call getInputStream() for the remainder. But if the return code is >=400, the header is read, and then:

InputStream instream = socket.getInputStream();
InputStream errstream = null;

// read header now.

// return code >= 400?

errstream = instream;
instream = null;


There you have it. There's only the one stream, from the socket. IN and ERR are flipped. You can't ever have both at the same time (note that this is different from an exec'd subprocess, but that's OS-level behavior for a process).

So the "content" that gets read from the socket is either the normal result, or it's the error message. Some messages are generated by the server (Tomcat), and some are from the servlet code.

OK, so no big deal to read all this. And yea, verily, with a couple initial errors readily fixed, it works as it is supposed to.

And I have a new tool.

And neither you nor I can trust HttpUrlConnection again. But with a new tool, who needs to?

Part 3 is the source for the final item. It isn't finished, I'm not handling the various other return codes, just those necessary for what I was doing at work. I'll fix up those things as I need to.

another java hacking story, part 1

for somewhat dumb contract-funding reasons, I am working on a new activity for a few months.

this activity is to improve a REST API written by others. we'll skip the reasons and flavor of how bad it is...which is bad. It is mostly functional, but the engineering on it is poor. Example: comments in the code are about 1% of what they ought to be, across ~300 files.

How it's organized:

Oracle--Hibernate--Java classes--JAX-RS--Tomcat--web client

Nothing too unusual about that. I have some Hibernate experience, from 8 years ago, but that too was code I inherited, also functional (and better done) so I didn't have to understand it too deep. I have SQL experience, REST experience, Tomcat experience...etc.so I can do this.

It takes me a while to get the stupid thing to run, because it's very fragile to assemble, and in fact no one knows exactly how to do it. That should tell you a fair amount about its origins--a project that was quite expensive and failed fairly badly. I don't know how many people lost their jobs over it (my employer was uninvolved). Not enough, I expect. Customer wants to probably have less egg on face, so we are carrying a small amount forward. But because of the origins, we don't completely know how to build from scratch. Well, we still don't, in an automated fashion, but I could hand-assemble it from scratch.

There's some amount of JUnit testing built in, when you run Maven to build .war files, but that's all broken. I'm not working that part, yet.

I need a client program to test with, so I can exercise the services thoroughly for a baseline, and then when I am making code changes. But of course there isn't one. No big deal, I have my own that I have been using for years.

I wrote my client program 10-12 years ago, far enough back that I don't actually remember when. I've used it a lot of times, generally with NanoHTTPD (another favorite piece of code, from Jarno Elonen; you can find it online, although I think what you can find now is not as good as it used to be--Jarno appeared to lose control over it several years ago, it got moved to sourceforge or github or something like that, and then go downhill; I have I think his final personal version). It is has always worked fine for me. Until this project.

Apparently all I've done with it is simplistic things. And now I have something harder. It errors out a lot as the client for this REST activity.

I have no idea what is going on, but I didn't bother with it for several weeks, needing to get other things going--but once I reached the point where I needed to be measuring success rates, those failures couldn't be tolerated.

So I dug in to find out what was going on. I've been doing sockets for 30 years, I can do this.

Turned out the failures all occurred when the server (Tomcat) was sending service results back with "Transfer-Encoding: Chunked". Chunked output has "Content-Length: -1" so you can't just read N bytes and be done.

My client program had always been using java.net.HttpUrlConnection -- and guess what? That doesn't quite work right with chunked returns. Well, HttpUrlConnection has been around since what looks like Day 1 for Java, so it predates the existence of "chunked", but you'd think maybe it would have been modified since then? Apparently not. OK, that means I have to do the handling/reading of it--fine, I can read sockets ok.

Or can I? I read up on what "chunked" means in terms of data formatting; it's not complicated, looks straightforward and easy to implement.

Well, yeah, sort of. I mean I wrote something to do it, only that fails every time. So I went back to simpler approach, and simply read all the bytes until nothing left--ahah! There's something missing!

Your normal result should be:

HTTP/1.1 200 OK
header
header

hex-value-of-chunk-length
chunk-bytes

hex-value-of-chunk-length
chunk-bytes

0


HttpUrlConnection is going to read all the header stuff for you, and then you start reading, which will be at the first "hex-value" point. But that seems to be not true, it appears that I am starting just after that. I horsed around with streams back and forth to try to figure out what was going on, to no avail. Could not get it to go.

So since this project has "Spring" around, I thought I would try that, too. Well, that did slightly better, it appeared to be reading all the bytes, but a bunch of times the result would be a big long string of spaces, not my content. WTF? This isn't new code either, but it too doesn't deal with chunked results properly.

So first off: why the chunking? I don't know. This seems to be something Tomcat is doing for me, the services don't appear to have any involvement, so it's not like I can turn it off. I am stuck.

And now I'm annoyed.

[Aside: there's a theme running through my professional career about occasions where I had a problem with someone else's code, and concluded that I would have to rewrite it from scratch in order to get something I could control, and assure that it worked correctly. And I'm the person who can do it.]

So now I'm at the point where I am going to have to rewrite it from scratch.

Tuesday, March 08, 2016

Elite Dangerous

Got this on the recommmendation of a co-worker.

Without a doubt, this is THE WORST GAME UI I HAVE EVER SEEN.

I quit permanently after entirely too much difficulty doing the most basic of things. Things that you are never going to do IRL.

Like docking. Docking will NEVER be a manual process where you steer by mouse. It will be 100% automated at all times. As will launch.

I simply don't have time to waste on this kind of crud. Fortunately I only paid a few $ for it.

Saturday, March 05, 2016

some java hacking

For some reason I got motivated to investigate this little problem. Finally. After I don't know how many years...

java.awt.BorderLayout doesn't let you put things in the corners.

The corners don't even exist in their own right.

All that exists are a bar across the top, full width, a similar bar at the bottom, and left/right sections in between those bars, and then the center that resizes to fill the gap.

 But what about when you want those corners?
 This is one approach:


It resembles the desired result, but is fairly hackish by using nested BorderLayouts. Ugh.
 Google "borderlayout corner" and you get a bunch of non-answers, all very hackish, all of which purport to do what you want, but have various drawbacks ("I would use nested JPanels with such-n-such" or some other garbage). Stackoverflow is where most of the discussion takes place, and the result is typical of SOF. GridBag (yuk), the above nested BorderLayouts, "put your widgets into a custom Border", etc; all yuk, all missing the target.

The basic answer is that there's no built-in way in awt/swing to do this. At all. Which is kinda sad.

Actually the custom border is almost the right approach.

-------------------

I had wanted this years ago, long enough back that I don't remember when the idea first occurred to me.

Should not take an Advanced Degree(tm) to figure this out. Altho looking at that SOF discussion, it might. Fortunately that's what we have here at Hyde U.

-------------------

The answer is of course that you have to make your own layout manager. That's not terribly hard, I've done it before, with a grid variation.




The NE corner is a JButton. The other corners are JLabels. The sides are JPanels, with one JLabel each. The center is just a JPanel. All with background colors that are hideous so you can see where the boundaries are. The center/east boundary appears to have a one-pixel glitch I haven't figured out.

In the code below, it's ok to only fill one corner. Remember that JButton uses a lot of margin space, so if you wanted (for example, and the reason I started on this finally) a tiny little "X" button @ NE, you're going to have to force the JButton to be the size you want.

All the proper resizing takes place, layout is dynamically computed to match whatever you are putting in the corners.

Of course this leads to a fairly horrible aesthetic around the sides because the corners can force the sides into sizes you don't like, but the whole thing DOES work. If you want something sized more like the first image, then you should go with that alternative. It's in the code at the bottom.

How'd I do this? I grabbed the Java 7 source for BorderLayout, and started banging on it. It's not quite finished, I want to strip out some extraneous garbage (the LTR stuff, and the PAGE_START stuff), hgap/vgap aren't used everywhere as spacers...

Took me about 3 hours. Longer than it should have, I went down a bad direction with cut-n-paste errors, had to start over.

Here it is:
(note that this is at least partially copyright Sun Microsystems, from way back when; you can find the Java 7 source code easily enough, and see the original I started from)

--------

package com.hu.Test;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;

// HU: jeez, I should have written this over a decade ago. Well, finally, here it is.

/**
 * A border layout lays out a container, arranging and resizing
 * its components to fit in five regions:
 * north, south, east, west, and center.
 *
 * This modified version ALSO allows you to use the corners to hold other components. HU, Mar, 2016.
 *
 * Each region may contain no more than one component, and
 * is identified by a corresponding constant:
 * NORTH, SOUTH, EAST,
 * WEST, and CENTER.  When adding a
 * component to a container with a border layout, use one of these
 * five constants, for example:
 *
 *    Panel p = new Panel();
 *    p.setLayout(new BorderLayout());
 *    p.add(new Button("Okay"), BorderLayout.SOUTH);
 * 

 * As a convenience, BorderLayout interprets the
 * absence of a string specification the same as the constant
 * CENTER:
 *
 *    Panel p2 = new Panel();
 *    p2.setLayout(new BorderLayout());
 *    p2.add(new TextArea());  // Same as p.add(new TextArea(), BorderLayout.CENTER);
 * 

 *
 * NOTE: Currently (in the Java 2 platform v1.2),
 * BorderLayout does not support vertical
 * orientations.  The isVertical setting on the container's
 * ComponentOrientation is not respected.
 *
 * The components are laid out according to their
 * preferred sizes and the constraints of the container's size.
 * The NORTH and SOUTH components may
 * be stretched horizontally; the EAST and
 * WEST components may be stretched vertically;
 * the CENTER component may stretch both horizontally
 * and vertically to fill any space left over.
 *
 *
 * @author      Arthur van Hoff (original), Hyde University (corners)
 * @see         java.awt.Container#add(String, Component)
 * @see         java.awt.ComponentOrientation
 * @see            java.awt.BorderLayout
 * @since       JDK1.0
 */
public class BorderLayout implements LayoutManager2, java.io.Serializable {
    /**
     * Constructs a border layout with the horizontal gaps
     * between components.
     * The horizontal gap is specified by hgap.
     *
     * @see #getHgap()
     * @see #setHgap(int)
     *
     * @serial
     */
    int hgap;

    /**
     * Constructs a border layout with the vertical gaps
     * between components.
     * The vertical gap is specified by vgap.
     *
     * @see #getVgap()
     * @see #setVgap(int)
     * @serial
     */
    int vgap;

    /**
     * Constant to specify components location to be the
     *      north portion of the border layout.
     * @serial
     * @see #getChild(String, boolean)
     * @see #addLayoutComponent
     * @see #getLayoutAlignmentX
     * @see #getLayoutAlignmentY
     * @see #removeLayoutComponent
     */
    Component north;
    /**
     * Constant to specify components location to be the
     *      west portion of the border layout.
     * @serial
     * @see #getChild(String, boolean)
     * @see #addLayoutComponent
     * @see #getLayoutAlignmentX
     * @see #getLayoutAlignmentY
     * @see #removeLayoutComponent
     */
    Component west;
    /**
     * Constant to specify components location to be the
     *      east portion of the border layout.
     * @serial
     * @see #getChild(String, boolean)
     * @see #addLayoutComponent
     * @see #getLayoutAlignmentX
     * @see #getLayoutAlignmentY
     * @see #removeLayoutComponent
     */
    Component east;
    /**
     * Constant to specify components location to be the
     *      south portion of the border layout.
     * @serial
     * @see #getChild(String, boolean)
     * @see #addLayoutComponent
     * @see #getLayoutAlignmentX
     * @see #getLayoutAlignmentY
     * @see #removeLayoutComponent
     */
    Component south;
    /**
     * Constant to specify components location to be the
     *      center portion of the border layout.
     * @serial
     * @see #getChild(String, boolean)
     * @see #addLayoutComponent
     * @see #getLayoutAlignmentX
     * @see #getLayoutAlignmentY
     * @see #removeLayoutComponent
     */
    Component center;

    Component northeast, southeast, northwest, southwest;

    /**
     * The north layout constraint (top of container).
     */
    public static final String NORTH  = "North";

    /**
     * The south layout constraint (bottom of container).
     */
    public static final String SOUTH  = "South";

    /**
     * The east layout constraint (right side of container).
     */
    public static final String EAST   = "East";

    /**
     * The west layout constraint (left side of container).
     */
    public static final String WEST   = "West";

    /**
     * The center layout constraint (middle of container).
     */
    public static final String CENTER = "Center";

    public static final String NORTHEAST = "Northeast";
    public static final String NORTHWEST = "Northwest";
    public static final String SOUTHEAST = "Southeast";
    public static final String SOUTHWEST = "Southwest";

    /*
     * JDK 1.1 serialVersionUID
     */
    private static final long serialVersionUID = -8658291919501921765L;

    /**
     * Constructs a new border layout with
     * no gaps between components.
     */
    public BorderLayout() {
        this(0, 0);
    }

    /**
     * Constructs a border layout with the specified gaps
     * between components.
     * The horizontal gap is specified by hgap
     * and the vertical gap is specified by vgap.
     * @param   hgap   the horizontal gap.
     * @param   vgap   the vertical gap.
     */
    public BorderLayout(int hgap, int vgap) {
        this.hgap = hgap;
        this.vgap = vgap;
    }

    /**
     * Returns the horizontal gap between components.
     * @since   JDK1.1
     */
    public int getHgap() {
        return hgap;
    }

    /**
     * Sets the horizontal gap between components.
     * @param hgap the horizontal gap between components
     * @since   JDK1.1
     */
    public void setHgap(int hgap) {
        this.hgap = hgap;
    }

    /**
     * Returns the vertical gap between components.
     * @since   JDK1.1
     */
    public int getVgap() {
        return vgap;
    }

    /**
     * Sets the vertical gap between components.
     * @param vgap the vertical gap between components
     * @since   JDK1.1
     */
    public void setVgap(int vgap) {
        this.vgap = vgap;
    }

    /**
     * Adds the specified component to the layout, using the specified
     * constraint object.  For border layouts, the constraint must be
     * one of the following constants:  NORTH,
     * SOUTH, EAST,
     * WEST, or CENTER,
     * NORTHEAST, NORTHWEST,
     * SOUTHEAST, SOUTHWEST
     *
     * Most applications do not call this method directly. This method
     * is called when a component is added to a container using the
     * Container.add method with the same argument types.
     * @param   comp         the component to be added.
     * @param   constraints  an object that specifies how and where
     *                       the component is added to the layout.
     * @see     java.awt.Container#add(java.awt.Component, java.lang.Object)
     * @exception   IllegalArgumentException  if the constraint object is not
     *                 a string, or if it not one of the five specified
     *              constants.
     * @since   JDK1.1
     */
    public void addLayoutComponent(Component comp, Object constraints) {
        synchronized (comp.getTreeLock()) {
            if ((constraints == null) || (constraints instanceof String)) {
                addLayoutComponent((String)constraints, comp);
            } else {
                throw new IllegalArgumentException("cannot add to layout: constraint must be a string (or null)");
            }
        }
    }

    /**
     * @deprecated  replaced by addLayoutComponent(Component, Object).
     */
    @Deprecated
    public void addLayoutComponent(String name, Component comp) {
        synchronized (comp.getTreeLock()) {
            /* Special case:  treat null the same as "Center". */
            if (name == null) {
                name = "Center";
            }

            /* Assign the component to one of the known regions of the layout.
             */
            if (CENTER.equals(name)) {
                center = comp;
            } else if (NORTH.equals(name)) {
                north = comp;
            } else if (SOUTH.equals(name)) {
                south = comp;
            } else if (EAST.equals(name)) {
                east = comp;
            } else if (WEST.equals(name)) {
                west = comp;

            } else if (NORTHEAST.equals(name)) {
                northeast = comp;
            } else if (SOUTHEAST.equals(name)) {
                southeast = comp;
            } else if (NORTHWEST.equals(name)) {
                northwest = comp;
            } else if (SOUTHWEST.equals(name)) {
                southwest = comp;

            } else {
                throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name);
            }
        }
    }

    /**
     * Removes the specified component from this border layout. This
     * method is called when a container calls its remove or
     * removeAll methods. Most applications do not call this
     * method directly.
     * @param   comp   the component to be removed.
     * @see     java.awt.Container#remove(java.awt.Component)
     * @see     java.awt.Container#removeAll()
     */
    public void removeLayoutComponent(Component comp) {
        synchronized (comp.getTreeLock()) {
            if (comp == center) {
                center = null;
            } else if (comp == north) {
                north = null;
            } else if (comp == south) {
                south = null;
            } else if (comp == east) {
                east = null;
            } else if (comp == west) {
                west = null;

            } else if (comp == northeast) {
                northeast = null;
            } else if (comp == southeast) {
                southeast = null;
            } else if (comp == northwest) {
                northwest = null;
            } else if (comp == southwest) {
                southwest = null;
            }

        }
    }

    /**
     * Gets the component that was added using the given constraint
     *
     * @param   constraints  the desired constraint, one of CENTER,
     *                       NORTH, SOUTH,
     *                       WEST, EAST,
     *                       NORTHEAST, NORTHWEST,
     *                       SOUTHEAST, SOUTHWEST
     * @return  the component at the given location, or null if
     *          the location is empty
     * @exception   IllegalArgumentException  if the constraint object is
     *              not one of the specified constants
     * @see     #addLayoutComponent(java.awt.Component, java.lang.Object)
     * @since 1.5
     */
    public Component getLayoutComponent(Object constraints) {

        if (CENTER.equals(constraints)) {
            return center;

        } else if (NORTH.equals(constraints)) {
            return north;
        } else if (SOUTH.equals(constraints)) {
            return south;
        } else if (WEST.equals(constraints)) {
            return west;
        } else if (EAST.equals(constraints)) {
            return east;

        } else if (NORTHEAST.equals(constraints)) {
            return northeast;
        } else if (SOUTHEAST.equals(constraints)) {
            return southeast;
        } else if (NORTHWEST.equals(constraints)) {
            return northwest;
        } else if (SOUTHWEST.equals(constraints)) {
            return southwest;

        } else {
            throw new IllegalArgumentException("cannot get component: unknown constraint: " + constraints);
        }
    }


    /**
     * Returns the component that corresponds to the given constraint location.
     *
     * @param   constraints     the desired absolute position, one of CENTER,
     *                          NORTH, SOUTH,
     *                          EAST, WEST,
     *                          NORTHEAST, NORTHWEST,
     *                          SOUTHEAST, SOUTHWEST
     * @param   target     the {@code Container} used to obtain
     *                     the constraint location based on the target
     *                     {@code Container}'s component orientation.
     * @return  the component at the given location, or null if
     *          the location is empty
     * @exception   IllegalArgumentException  if the constraint object is
     *              not one of the specified constants
     * @exception   NullPointerException  if the target parameter is null
     * @see     #addLayoutComponent(java.awt.Component, java.lang.Object)
     * @since 1.5
     */
    public Component getLayoutComponent(Container target, Object constraints) {
        //boolean ltr = target.getComponentOrientation().isLeftToRight();
        Component result = null;

        if (NORTH.equals(constraints)) {
            result = north;
        } else if (SOUTH.equals(constraints)) {
            result = south;
        } else if (WEST.equals(constraints)) {
            result = west;
        } else if (EAST.equals(constraints)) {
            result = east;
       
        } else if (NORTHEAST.equals(constraints)) {
            result = northeast;
        } else if (SOUTHEAST.equals(constraints)) {
            result = southeast;
        } else if (NORTHWEST.equals(constraints)) {
            result = northwest;
        } else if (SOUTHWEST.equals(constraints)) {
            result = southwest;

        } else if (CENTER.equals(constraints)) {
            result = center;

        } else {
            throw new IllegalArgumentException("cannot get component: invalid constraint: " + constraints);
        }

        return result;
    }


    /**
     * Gets the constraints for the specified component
     *
     * @param   comp the component to be queried
     * @return  the constraint for the specified component,
     *          or null if component is null or is not present
     *          in this layout
     * @see #addLayoutComponent(java.awt.Component, java.lang.Object)
     * @since 1.5
     */
    public Object getConstraints(Component comp) {
        //fix for 6242148 : API method java.awt.BorderLayout.getConstraints(null) should return null
        if (comp == null){
            return null;
        }
        if (comp == center) {
            return CENTER;
        } else if (comp == north) {
            return NORTH;
        } else if (comp == south) {
            return SOUTH;
        } else if (comp == west) {
            return WEST;
        } else if (comp == east) {
            return EAST;

        } else if (comp == northwest) {
            return NORTHWEST;
        } else if (comp == southwest) {
            return SOUTHWEST;
        } else if (comp == northwest) {
            return NORTHWEST;
        } else if (comp == southwest) {
            return SOUTHWEST;
        }
        return null;
    }

    /**
     * Determines the minimum size of the target container
     * using this layout manager.
     *
     * This method is called when a container calls its
     * getMinimumSize method. Most applications do not call
     * this method directly.
     * @param   target   the container in which to do the layout.
     * @return  the minimum dimensions needed to lay out the subcomponents
     *          of the specified container.
     * @see     java.awt.Container
     * @see     java.awt.BorderLayout#preferredLayoutSize
     * @see     java.awt.Container#getMinimumSize()
     */
    public Dimension minimumLayoutSize(Container target) {
        synchronized (target.getTreeLock()) {
            Dimension dim = new Dimension(0, 0);

            boolean ltr = target.getComponentOrientation().isLeftToRight();
            Component c = null;

            if ((c=getChild(EAST,ltr)) != null) {
                Dimension d = c.getMinimumSize();
                dim.width += d.width + hgap;
                dim.height = Math.max(d.height, dim.height);
            }
            if ((c=getChild(WEST,ltr)) != null) {
                Dimension d = c.getMinimumSize();
                dim.width += d.width + hgap;
                dim.height = Math.max(d.height, dim.height);
            }
            if ((c=getChild(CENTER,ltr)) != null) {
                Dimension d = c.getMinimumSize();
                dim.width += d.width;
                dim.height = Math.max(d.height, dim.height);
            }
            if ((c=getChild(NORTH,ltr)) != null) {
                Dimension d = c.getMinimumSize();
                dim.width = Math.max(d.width, dim.width);
                dim.height += d.height + vgap;
            }
            if ((c=getChild(SOUTH,ltr)) != null) {
                Dimension d = c.getMinimumSize();
                dim.width = Math.max(d.width, dim.width);
                dim.height += d.height + vgap;
            }

            if ((c=getChild(NORTHEAST,ltr)) != null) {
                Dimension d = c.getMinimumSize();
                dim.width += d.width + hgap;
                dim.height = d.height + vgap;
            }

            Insets insets = target.getInsets();
            dim.width += insets.left + insets.right;
            dim.height += insets.top + insets.bottom;

            return dim;
        }
    }

    /**
     * Determines the preferred size of the target
     * container using this layout manager, based on the components
     * in the container.
     *
     * Most applications do not call this method directly. This method
     * is called when a container calls its getPreferredSize
     * method.
     * @param   target   the container in which to do the layout.
     * @return  the preferred dimensions to lay out the subcomponents
     *          of the specified container.
     * @see     java.awt.Container
     * @see     java.awt.BorderLayout#minimumLayoutSize
     * @see     java.awt.Container#getPreferredSize()
     */
    public Dimension preferredLayoutSize(Container target) {
        synchronized (target.getTreeLock()) {
            Dimension dim = new Dimension(0, 0);

            boolean ltr = target.getComponentOrientation().isLeftToRight();
            Component c = null;

            if ((c=getChild(EAST,ltr)) != null) {
                Dimension d = c.getPreferredSize();
                dim.width += d.width + hgap;
                dim.height = Math.max(d.height, dim.height);
            }
            if ((c=getChild(WEST,ltr)) != null) {
                Dimension d = c.getPreferredSize();
                dim.width += d.width + hgap;
                dim.height = Math.max(d.height, dim.height);
            }
            if ((c=getChild(CENTER,ltr)) != null) {
                Dimension d = c.getPreferredSize();
                dim.width += d.width;
                dim.height = Math.max(d.height, dim.height);
            }
            if ((c=getChild(NORTH,ltr)) != null) {
                Dimension d = c.getPreferredSize();
                dim.width = Math.max(d.width, dim.width);
                dim.height += d.height + vgap;
            }
            if ((c=getChild(SOUTH,ltr)) != null) {
                Dimension d = c.getPreferredSize();
                dim.width = Math.max(d.width, dim.width);
                dim.height += d.height + vgap;
            }

            if ((c=getChild(NORTHEAST,ltr)) != null) {
                Dimension d = c.getPreferredSize();
                dim.width += d.width + hgap;
                dim.height = d.height + vgap;
            }

            Insets insets = target.getInsets();
            dim.width += insets.left + insets.right;
            dim.height += insets.top + insets.bottom;

            return dim;
        }
    }

    /**
     * Returns the maximum dimensions for this layout given the components
     * in the specified target container.
     * @param target the component which needs to be laid out
     * @see Container
     * @see #minimumLayoutSize
     * @see #preferredLayoutSize
     */
    public Dimension maximumLayoutSize(Container target) {
        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
    }

    /**
     * Returns the alignment along the x axis.  This specifies how
     * the component would like to be aligned relative to other
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     */
    public float getLayoutAlignmentX(Container parent) {
        return 0.5f;
    }

    /**
     * Returns the alignment along the y axis.  This specifies how
     * the component would like to be aligned relative to other
     * components.  The value should be a number between 0 and 1
     * where 0 represents alignment along the origin, 1 is aligned
     * the furthest away from the origin, 0.5 is centered, etc.
     */
    public float getLayoutAlignmentY(Container parent) {
        return 0.5f;
    }

    /**
     * Invalidates the layout, indicating that if the layout manager
     * has cached information it should be discarded.
     */
    public void invalidateLayout(Container target) {
    }

    /**
     * Lays out the container argument using this border layout.
     *
     * This method actually reshapes the components in the specified
     * container in order to satisfy the constraints of this
     * BorderLayout object. The NORTH
     * and SOUTH components, if any, are placed at
     * the top and bottom of the container, respectively. The
     * WEST and EAST components are
     * then placed on the left and right, respectively. Finally,
     * the CENTER object is placed in any remaining
     * space in the middle.
     *
     * And the corners work as well, with NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST.
     *
     * Most applications do not call this method directly. This method
     * is called when a container calls its doLayout method.
     * @param   target   the container in which to do the layout.
     * @see     java.awt.Container
     * @see     java.awt.Container#doLayout()
     */
    public void layoutContainer(Container target) {
        synchronized (target.getTreeLock()) {
            Insets insets = target.getInsets();
            int top = insets.top;
            int bottom = target.getHeight() - insets.bottom;
            int left = insets.left;
            int right = target.getWidth() - insets.right;

            boolean ltr = target.getComponentOrientation().isLeftToRight();
            Component c = null;

            // order is somewhat important in the original, because there was an implied ordering to what was going on, top->bottom, L->R

            // now there's not.
           
            // we have to manage all 8 edge pieces together.
           
            Dimension zero = new Dimension(0, 0);
           
            Dimension nw = northwest != null ? northwest.getPreferredSize() : zero;
            Dimension no = north != null ? north.getPreferredSize() : zero;
            Dimension ne = northeast != null ? northeast.getPreferredSize() : zero;

            Dimension we = west != null ? west.getPreferredSize() : zero;
            Dimension ea = east != null ? east.getPreferredSize() : zero;

            Dimension sw = southwest != null ? southwest.getPreferredSize() : zero;
            Dimension so = south != null ? south.getPreferredSize() : zero;
            Dimension se = southeast != null ? southeast.getPreferredSize() : zero;
                       
            // boundary sizes.
           
            int lwid = Math.max(Math.max(nw.width, we.width), sw.width);
            int rwid = Math.max(Math.max(ne.width, ea.width), se.width);
            int thei = Math.max(Math.max(nw.height, no.height), ne.height);
            int bhei = Math.max(Math.max(sw.height, so.height), se.height);
           
            // each of these methods resizes the widget: setSize, setBounds

            if ((c=getChild(NORTH,ltr)) != null) {
                c.setBounds(left+lwid, top, right - left - lwid - rwid, thei);
            }
                       
            if ((c=getChild(SOUTH,ltr)) != null) {
                c.setBounds(left+lwid, bottom - bhei, right - left - lwid - rwid, bhei);
            }
           
            if ((c=getChild(EAST,ltr)) != null) {
                c.setBounds(right - rwid, top+thei, rwid, bottom - top - thei - bhei);
            }
           
            if ((c=getChild(WEST,ltr)) != null) {
                c.setBounds(left, top+thei, lwid, bottom - top - thei - bhei);
            }
           
            if ((c=getChild(CENTER,ltr)) != null) {
                c.setBounds(left + lwid, top + thei, right - left - rwid - lwid, bottom - top - thei - bhei);
            }
           
            if ((c=getChild(NORTHEAST,ltr)) != null) {
                c.setBounds(right-rwid, top, rwid, thei);
            }
           
            if ((c=getChild(SOUTHEAST,ltr)) != null) {
                c.setBounds(right-rwid, bottom - bhei, rwid, bhei);
            }
           
            if ((c=getChild(NORTHWEST,ltr)) != null) {
                c.setBounds(left, top, lwid, thei);
            }
            if ((c=getChild(SOUTHWEST,ltr)) != null) {
                c.setBounds(left, bottom-bhei, lwid, bhei);
            }
        }
    }

    /**
     * Get the component that corresponds to the given constraint location
     *
     * @param   key     The desired absolute position,
     *                  either NORTH, SOUTH, EAST, or WEST.
     *                  Or NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST.
     * @param   ltr     Is the component line direction left-to-right?
     */
    private Component getChild(String key, boolean ltr) {
        Component result = null;

        if (key == NORTH) {
            result = north;
        }
        else if (key == SOUTH) {
            result = south;
        }
        else if (key == WEST) {
            result = west;
        }
        else if (key == EAST) {
            result = east;
        }

        else if (key == NORTHWEST) {
            result = northwest;
        }
        else if (key == SOUTHWEST) {
            result = southwest;
        }
        else if (key == NORTHEAST) {
            result = northeast;
        }
        else if (key == SOUTHEAST) {
            result = southeast;
        }

        else if (key == CENTER) {
            result = center;
        }
        if (result != null && !result.isVisible()) {
            result = null;
        }
        return result;
    }

    /**
     * Returns a string representation of the state of this border layout.
     * @return    a string representation of this border layout.
     */
    public String toString() {
        return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + "]";
    }
}

--------------------
Here's the test case:

(There are four different approaches in here:)

package com.hu.Test;

//import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;

import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class BorderCorners {

    public static void main(String[] args) {
       
        JPanel mainPanel = new JPanel(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints(0, 0, 2,2, 1.0, 1.0,
                GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(
                        0, 0, 0, 0), 0, 0);

        mainPanel.add(new JLabel("Left Upper"), gbc);

        gbc.gridx = 2;
        gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.NORTHEAST;
        mainPanel.add(new JLabel("Right Upper"), gbc);

        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.anchor = GridBagConstraints.SOUTHWEST;
        mainPanel.add(new JLabel("Left Lower"), gbc);

        gbc.gridx = 2;
        gbc.gridy = 2;
        gbc.anchor = GridBagConstraints.SOUTHEAST;
        mainPanel.add(new JLabel("Right Lower"), gbc);

        JFrame frame = new JFrame("Grid Bag Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);

        // well, this ends up in the middle all right, but it doesn't size up/down when you resize the frame.
        JPanel centerPanel = new JPanel();
        centerPanel.setBackground(Color.red);
        gbc.gridx = 1;
        gbc.gridy = 1;
        gbc.anchor = GridBagConstraints.CENTER;
        mainPanel.add(centerPanel, gbc);


        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setVisible(true);

        // ***********************

        //yep, this misses the target too.

        JPanel northPanel = new JPanel(new java.awt.BorderLayout());     
        northPanel.add(new JLabel("North East"), java.awt.BorderLayout.EAST);
        northPanel.add(new JLabel("North West"), java.awt.BorderLayout.WEST);

        JPanel southPanel = new JPanel(new java.awt.BorderLayout());
        southPanel.add(new JLabel("South East"), java.awt.BorderLayout.EAST);
        southPanel.add(new JLabel("South West"), java.awt.BorderLayout.WEST);


        mainPanel = new JPanel(new java.awt.BorderLayout());
        mainPanel.add(northPanel, java.awt.BorderLayout.NORTH);
        mainPanel.add(southPanel, java.awt.BorderLayout.SOUTH);

        centerPanel = new JPanel();
        centerPanel.setBackground(Color.red);
        mainPanel.add(centerPanel, java.awt.BorderLayout.CENTER);

        JPanel left = new JPanel();
        left.setBackground(Color.YELLOW);
        mainPanel.add(left, java.awt.BorderLayout.WEST);

        JPanel right = new JPanel();
        right.setBackground(Color.GREEN);
        mainPanel.add(right, java.awt.BorderLayout.EAST);
       
        frame = new JFrame("AWT BorderLayout Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.setLocationByPlatform(true);
       
        frame.setSize(300, 300);
        frame.setVisible(true);

        // ***********************

        System.out.println("trying now with a copy of border layout");

        mainPanel = new JPanel(new BorderLayout());

        JButton xx = new JButton("X");
        System.out.println(xx.getInsets());
        Insets xxi = xx.getInsets();
        xxi.set(2, 2, 2, 2);
        xx.setMargin(xxi);
        //JLabel xx = new JLabel("X");
        xx.setOpaque(true);
        xx.setBackground(Color.MAGENTA);
        xx.setForeground(Color.white);

        mainPanel.add(xx, BorderLayout.NORTHEAST);
        mainPanel.add(new JLabel("North W"), BorderLayout.NORTHWEST);
        mainPanel.add(new JLabel("South East"), BorderLayout.SOUTHEAST);
        mainPanel.add(new JLabel("South W"), BorderLayout.SOUTHWEST);

        northPanel = new JPanel();
        northPanel.setBackground(Color.PINK);
        northPanel.add(new JLabel("N"));
        mainPanel.add(northPanel, BorderLayout.NORTH);

        southPanel = new JPanel();
        southPanel.setBackground(Color.CYAN);
        southPanel.add(new JLabel("S"));
        mainPanel.add(southPanel, BorderLayout.SOUTH);

        centerPanel = new JPanel();
        centerPanel.setBackground(Color.red);
        //mainPanel.add(centerPanel, BorderLayout.CENTER);
        mainPanel.add(centerPanel);
       
        left = new JPanel();
        left.setBackground(Color.YELLOW);
        left.add(new JLabel("W"));
        mainPanel.add(left, BorderLayout.WEST);

        right = new JPanel();
        right.setBackground(Color.GREEN);
        right.add(new JLabel("E"));
        mainPanel.add(right, BorderLayout.EAST);

        frame = new JFrame("NEW BorderLayout Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);

        frame.setSize(300, 300);
        frame.setVisible(true);
       
        // ***********************

        // this isn't right either. but it's slightly better.
        frame = new JFrame("Group L");


        JLabel labelNW = new JLabel("NW");
        JButton labelNE = new JButton("NE");
        JLabel labelSW = new JLabel("SW");
        JLabel labelSE = new JLabel("SE");


        GroupLayout layout = new GroupLayout(frame.getContentPane());
        layout.setHorizontalGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(Alignment.LEADING)
                        .addComponent(labelNW)
                        .addComponent(labelSW))
                        .addGap(20,50,Short.MAX_VALUE)
                        .addGroup(layout.createParallelGroup(Alignment.TRAILING)
                                .addComponent(labelNE)
                                .addComponent(labelSE))
                );
        layout.setVerticalGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(Alignment.LEADING)
                        .addComponent(labelNW)
                        .addComponent(labelNE))
                        .addGap(20,50,Short.MAX_VALUE)
                        .addGroup(layout.createParallelGroup(Alignment.TRAILING)
                                .addComponent(labelSW)
                                .addComponent(labelSE))
                );
        frame.getContentPane().setLayout(layout);
        frame.getContentPane().setBackground(Color.LIGHT_GRAY);
        frame.setSize(200, 200);
        frame.setVisible(true);

    }
}