Thursday, November 10, 2016

New job

Gad. I almost got forcibly retired a month ago. Previous employer been having trouble all year about keeping me billable. Multiple hiccups caused me to be on overhead almost five months.

At my skill/experience level, that's expensive for them, so in October I went on unpaid LOA. That's a gamble by everyone that work is going to show up. My suspicion was not, so I didn't wait around much.


Now don't get me wrong, here...it's not that I *wanted* to get a job again. I'd have rather retired officially, but I can't quite afford that yet (recall that a move is occurring) until I sell the old house--once that's done I can retire when I feel like it.

New job is forcing a 25% pay cut on me, tho. That stings a bit. Again, once the old house is sold, that won't matter too much. But still...

That said--new job is properly interesting. Have a software AND hardware problem to solve. And multiple installations eventually, altho not too many. [Later: weird prime contractor goings on--this is dead.]

And its a really different tech area than I've done before, which means new learning and challenges.

[Update: it's a tiny business, maybe 10 employees. Luckily, a couple of them are fans of ethnic foods, as I am, so there's a small crew for lunch daily. That hasn't happened in >8 years. Yay!]

-----

While I was on LOA I started building the barn. Regrettably that's Plan C. Plan A was someone else builds a metal building. Plan B was someone else builds a wood building. Plan C was I built the wood building because I can't afford to pay anyone else; of course in the end that means I can say "I built it". It also means that if it falls down "I built that". So I've been getting a crash course in construction on this. Mistakes so far are minor, other than the weird need for after-the-fact anchor bolts into the concrete foundation. You really want to do that when its poured. Now I have to drill for them. If I had to do it over again, I'd go with cinder block walls, pay an expert to do that.

[Update: the walls went up in Nov. The roof trusses went up after xmas. The metal roofing went up a few days later. I am not the monkey I used to be for climbing on that stuff. Not quite done as of Jan 1, but not too much left. Eventually I'll put solar panels on it, probably 3kW.]

-----

Much later: turned out this job was really a bad idea. 11 months after I started it, and not quite six months after it ended, I *STILL* do not have a W2 for 2016. I am not sure if that equals stupid incompetence, criminal incompetence, or both, but it's damn sure incompetence. Some of the payroll taxes weren't even paid to the IRS. That's borderline criminal incompetence.

And there were never any paystubs I could fake/create a W2 from. So I have no info about withholding.

I may be having to sue them here shortly.

Odd trouble with Apple Mail

Various Mac users in the extended family. Have been for years (since the late 80s for me).

So I've a lot of experience with nearly every version of OS Apple has ever produced.

My mom bought a new Macbook in the summer. Her old machine was getting flaky, it was like 10 years old, so not a huge surprise.

Her old OS was 10.4, new OS is 10.11 (El Cap).

She's using Apple Mail. I have occasionally used that in the past, but only on my Powerbook laptop, and even then only on vacation, where email need was limited at best.

Mom's email flakes out regularly, refusing to send to the SMTP server. So I have gotten a lot of "tech support calls" from her about this, and I haven't really been able to solve it. Have spent time on the phone with her ISP, and with Apple, trying to figure this out.

IT SHOULD NOT TAKE AN ADVANCED DEGREE(TM) TO FIX THIS.

Except that apparently it does. Pay attention to get yours.

So here's what is going on (Apple guy didn't know this stuff, ISP guy hinted at it):

Apple Mail ("AM") auto-configures an account for you based on an email address you give it when you first start it up, and you can add more later that will also auto-configure. OK, that's friendly, you don't have to be an expert on setup to get going on your brand-new machine.

AM now knows how to handle additional mail flavors, so you can do IMAP as well as POP. These two things aren't the same however, so it's interesting that it auto-configures for you. SMTP has gotten fancier over the years with security settings, and that's where the problem starts.

The long-term problem is that it continues to try to auto-configure for you--any time there's a glitch between your machine and the server, it attempts to re-auto-configure the entire account. This is a thing that AM didn't used to do, so it took me a long time to figure it out.

Well, the odds of that working right are probably slim. Fortunately, it looks like you can turn this off, which is probably the right thing to do immediately. There's a check-box on your account settings about "auto-configure" that you really want un-checked.

The problem mom was experiencing is that the auto-configure was going from port 587/password/SSL to port 25/MD5/SSL, and the ISP just doesn't do that. And AM wouldn't let go of it most of the time. The ISP does not change it settings. Not ever. Yours doesn't either.

And this "re-auto-configure" would take place any time there was any kind of comm hiccup between AM and ISP/SMTP server. This was, I believe, the real problem. Mom's ISP is just not able to keep up with its workload, then there are comm-channel hiccups, if you try to send email during one of those hiccups it tries to reconfigure the comm scheme by choosing some other settings, that fails because the ISP only uses the one group of settings

It seems like sometimes it would recover ok (like the settings got changed back ?!), outgoing mail would finally transmit, and then later there'd be another hiccup, outgoing mail wouldn't go out, and this would last for hours or even days.

So what you need to do is turn off the auto-configure-settings on your email accounts. Once you have the right settings, don't let AM change them again.

A few weeks after that discovery I found the help web-page on the ISP's own site that explained this whole things with pictures and red circles over the naughty check-box.

---

What's also interesting here is that I still have an account with that same ISP, for a few more months. I don't use AM, I use SeaMonkey (altho not for much longer, as it's degraded, because it's a Mozilla product, (see other blog on that topic)). SeaMonkey is a whole different program, doesn't do this auto-configure at all. And I'm not using the same program settings anyway--I use the POP service, weaker security, etc., and virtually never have trouble at all--certainly nothing like what mom has had.

So I've convinced myself that it's this auto-configure that starts with what WAS once good settings, and gets stupid about them, and doesn't recover.

So turn that off. Bad dog.

Friday, October 07, 2016

New SF reading

The end of 2015 there was this new show on SYFY called The Expanse. Based on some books I had not heard of or read.

So recently I've bought the books and read the first three, started the fourth.

Well...you know how this general sort of thing goes, it applies to music as well...every so often you make a discovery of a new artist whose work knocks your socks off. Ozric Tentacles was like that, Lana Lane was like that, Klaus Schulze was like that, ELP was like that...

Peter Hamilton was like that 15 years ago. Ron Goulart and PG Wodehouse were like that 35 years ago.

and The Expanse series is like that too. There's nothing in there that you haven't encountered before if you've been reading SF for a while. I've been reading SF since the late 1960s, so I've read it all. But The Expanse is like all those individual things rolled into one.

It's politics, except on the interplanetary scale, but not interstellar scale. It's got your kinda-typical hero, serious bad guys on a scale and detail not usually covered in SF, it's got classic space opera goings-on, unseen and incomprehensible aliens, artifacts left behind by those aliens that are either mega-nifty or mega-dangerous or both, spaceship battles...

I was impressed. So I'm reading the 4th one, bought the 5th, and awaiting the 6th.

I liked the tv show, but it was well-nigh incomprehensible without the books, and I think missed some episodes, making it REALLY hard to understand. And it was incomplete; the first tv season maybe covered the first half of book one--and THAT wouldn't have made sense without the rest of the book.

[Update: halfway through book six, not sure I can go on. SPOILER: Naomi's son decides it's cool to effectively destroy mankind on mother earth. Not yet clear how many people he murders by dropping asteroids on us. Hundreds of millions. Needs some necromancy, so he can be executed, then resurrected and executed again, at least once a day until the sun goes cold. All because he had some abandonment issues, and a manipulative daddy.]

---

So this all brings to mind something else. SYFY seems to have changed their game plan ~2 years ago and has stopped making those complete trash low-budget things they were doing for so long. And now we have some far-better-production-values-and-budget shows getting created. OK, and silly stuff like Sharknado.

I like this too. SYFY had devolved into too much fairly bad stuff. Well, it still has some fairly bad stuff, but Killjoys, Expanse, Childhood's End...now if they'd just drop WWE and Ghost Hunters and that kind of dreck. There's lots of stuff better than that they could re-run, and heck they could run those old cheesy 50s monster movies too, and all the star treks, etc. The least of which is better than the low-budget crud they put on.

Job Hunting

egad. it's job hunting time again. I am tired of this...it's not quite retirement time, altho it's close...

Earlier this year was my 40th (yes, 40) anniversary of when I started writing computer programs. Jan 1976. So I've been at it a while. And I'm very good at it.

My employer is not so good at getting new business, these days. Result: a year ago I had a project I was running. That collapsed in Feb due to a mgmt error re funding, and some customer error that we got blamed for also re funding; I had no idea anything was wrong until mgmt said "everybody stop charging this project today." In the spring I wrote a proposal for something new, that dawdled along until late Aug while the customer kept trying to get us to cut our price (while the scope and certainty of failure got larger), and we finally had to say no we aren't going to do this. Then I helped write another proposal that would start Oct 1, and we lost that on price to another group that is going to fail to perform by virtue of having to put really cheap (i.e., inexperienced) staff in 90% of the positions.

And now there's nothing else coming up for me. That's THREE fails this year. So my employer is going to have to lay me off in the next few days. I've been asked to give numbers for doing some contract work. I've never done that before...

So now I'm trying to figure out how to become an actual contractor.

Had an interview today at lunch with another employer just 3-4 miles from my old house...that'd be convenient for a while...we didn't talk about my planning to move and sell my old house in a few more months. I didn't think that was a good topic for this first meeting :)

I'd rather just retire, but I can't afford to do that until after the old house gets sold.

Yesterday I was at the new house. Because I was in the basement, I counted just how many model railroad structure kits I have to build...it's about 500, which means that if I build one a month I'll die before I get too far...one a week takes a decade to complete, and there's not way the bigger ones are only going to take a week--nor would I want them too, lest I be making mistakes all over.

Which means I need to get busy building these things...i.e., I gotta retire.

Which i can't quite afford to do. I gotta figure out how to work remote some of the time, how much to bill for that, etc.

----

Next day: agreed with my employer to take a 30-day LOA. Now I can build my outbuilding!

Monday, August 29, 2016

Presidential Election 2016

This has certainly been the craziest election season. End of August now. Trump is possibly the worst candidate who's ever run. Hillary has some massive negatives (whether true or not). Either side should be able to slam-dunk the other, but it ain't happening.

Why?

Hillary is polling ahead, although not excessively so. Do the polls tell you much? Those involved certainly think so, but there are some spectacular failures in the not-too-distant past.

You can readily find speculation that there are a lot of R people who will secretly vote for Hillary but not say so in advance. And that there are R people who are saying now that they won't vote for Trump.

I suspect that this is wrong. I think that what's likelier is that people who have always voted R will do so again, because once they are in the voting booth, they will simply be unable to vote for her. Trump gets the same voters Romney did. All of them.

So those non-crazy Trump voters...what are they thinking?

Voting for Trump is a protest vote. Simple as that. It doesn't matter one bit about whatever his crazy statements are, that's not important...what is important is that he is so non-politician that he will really shake things up.

I know about protest voting. I voted for Ross Perot in 1992. Ross would not have been that good a President, he was a little wacko, although not like Trump. I knew that Ross wouldn't win, but I didn't like GHWB or Bill C.

(aside: recall 1991: Gulf War 1. GHWB handled that well, although you could argue that he should have rolled on to Baghdad anyway. By late 91 he had the highest approval rating a President ever had, like 93%--he could have achieved ANYTHING. And he wasted it, because he had no vision. And when he was running again the next year "I'd like to be there to handle whatever comes up" was his reason for running. Brilliant. Doofus.)

So all the questions today on the D side about "what are these Trump voters thinking?" They have misunderstood completely. It's about protest, nothing else is relevant. Need to shake up Washington really bad. Trump will certainly do that, Hillary will not.

Of course Trump's crazy supporters want all non-white people booted out of the country. Or eligible for target practice, like in "The Purge". As though that will fix anything. As though the white folks who don't have a job right now would take a suddenly vacated job being done by someone brown.

So time for the inevitable prediction: well, several of them.

Option 1: Hillary 50, Trump 48, Johnson1, Stein 1.
Option 2: Trump 50, Hillary 48, etc.
Option 3: Hillary 60, Trump 30, Johnson 5, Stein 5.

I think option one is the likeliest. I think option 3 is least likely--Trump's "floor" as it is called, is not the 28% you can read about, it's more like 46%. All those Rs who've said they won't vote for him are lying, to themselves and to everyone else.

Option 3 happens if all those who were R but say they will vote D actually do so. I find this unlikely, but if the "women who vote for Hillary because it's the first chance [only chance] they get to vote for a woman" do in fact vote for her, I think Hillary gets 52%

Remember what happened in 2008 when the first black man was a candidate. Lots of votes for him for just that reason. He didn't have the negatives.

I also think:

Warren 65, Trump 25, Johnson 5, Stein 5.

Elizabeth Warren has the positives without the negatives, and picks up the Bernie voters. She crushes Trump. But it'll never happen...she's too old for a retry in 8 years.

Sunday, July 17, 2016

It shouldn't be this easy...

I got the new DOOM version on the last day of Steam summer sale. Half-price, good deal.

It only took me about four hours to find a bug in the game.

Really, people? This is neither a new problem or needing a new solution.

Does it take an Advanced Degree (tm) to figure this out? Surely not...

and yet, here we are again.

The situation:  this is the outside area before you move on to the Foundry, so it's early in the game. I've run into a group of opponents. Most are "possessed", shamblers like zombies. One of them is one of those guys with the blue full-height energy shield; they are tricky, your weapons at that point are completely inadequate for this, unless you manage some other advantage. So I run backwards, out through the airlock, and wait. Actually, this isn't a good idea, it's going to allow them all to catch up to me.

The airlock door is going to be very strong--it's an airlock. But it's also mostly transparent, so I can see them and they can see me. And I can see them wiggling around as though the path-finding math is trying alternate movement plans to get at me.

The thing with the shield has an organic "machine-gun" thing as its right arm. The thing has a center of gravity, that CoG is stuck on the other side of the door, it can't come through. But apparently the weapon has its own independent CoG, and that isn't being given position test that keeps it clipped behind the door.

So the gun is actually on MY side of the airlock door, while the thing's body is on the OTHER side of the door. And I don't realize that this is a bug until it kills me.

And then you bump into the other major-league suck about DOOM and that is what's casually known as "checkpointed saves". Which means I have to run this whole level over. More than once, it turned out, while trying to figure out how to attack properly, although I only got clipping-bug-killed once. (So the guy with the shield is a little slow, you can run around behind the shield and blast it in the back.)

and I have no idea how to report this bug. I don't know how to take a screen-snap, so I don't even have proper evidence that it happened. Others do know, so you can go look for something like this: google for "doom arrow knee", and yes, you'll see a long-dead humanoid body with a Skyrim helmet and an arrow in its knee.

Back to the original point: it should NOT take an Advanced Degree (tm) to understand that the gun has to exist on the same side of a door as the body, and therefore CANNOT mathemagically kill me.

Sheesh.

And of course having to take on two Hell Knights at the same time is a disaster. You should avoid that. I backed down to difficulty=1 for that.


Later: couple of updates. I crashed out to desktop around 10 hours. Just the one time, not sure why. This game is a massive resource hog--my PC is a quad-core, and all four are running over 90%. Plus, RAM is maxed out over 90% too. I can imagine those resources being the cause of a seg-fault.

Glory kill of one of the demons is amusing: you rip its heart out, and then stuff that heart down its throat, and it then explodes. There's a different one where you rip its arm off and bludgeon it with the arm.

Major flaw #2: WAY too much jumping. I hate that. I use a bluetooth mouse and keyboard. That REALLY doesn't work with this jumping crap.

And now, I've just "returned from Hell" and I'm stuck. There seems to be no proceed point. You run around a small amount, on what looks like a self-contained area of the map, from which you cannot proceed to the rest of it. It's showing no named task assignment, no description of what I'm doing next. And I can't see how to get off the map area I'm on. Dude--where's my jetpack? (Later: watched someone else's video-capture walkthrough, saw that there was another jump point upwards that leads on.)

Flaw #3: this game basically forces you to play it their way. Not what I want to do. There is a map-load message that says something to the effect of "standing still gets you killed". I want to do a slow play-through where I get to sneak/crouch/crawl around and eliminate things one at a time without getting into a run-n-gun with 4 or 5 Hell Knights and a couple of those flying shoulder-rocket things and other stuff. So I'm playing on god mode. Feels like Ultra-V--no way this is difficulty 1.

It's gorgeous, but I can't recommend this game. I may play through to the end, but I won't do it again.

Last: well, I won't even be playing through to the end. Remember Flaw #1? Checkpointed saves? Yeah, that doesn't work. Not completely. Started up again, having watched a video walkthrough, knowing what to do next, and the game says my one-and-only (because checkpoints, right?) save is corrupted and cannot load. How to proceed? Well, start over.

Goodbye. It's pretty but it sucks.

Later later: forgot this. Flaw #4: Another bug. There was a demon that had somehow spawned inside a wall (well, not that exactly, but on the opposite side of space where I can see it and shoot it, somehow inside. It was the one that throws fireballs. So being where it is, the walls behave differently in the math, it can see me, and throw fireballs at me THRU THE WALLS).

I guess it DOES take an Advanced Degree (tm) to do this right.

One last thought. It was entertaining seeing the demons occasionally attack each other. That didn't happen often enough. There were a few places in Quake 1 where that could happen, but it was rare.

Friday, April 15, 2016

Web browsers

How many do you use?

Here at Hyde U, it's Sea Monkey, Firefox, Safari.

Likely to NOT include SeaMonkey, pretty soon...and maybe Firefox goes away too.

Why? I've been using Mozilla-origin tools since Netscape 1.0 (an old friend, jwz, was one of the authors).

But Mozilla-originated tools are pretty poor these days. SeaMonkey is the worst, for sure, which is unfortunate because that didn't used to be true.

Why?

Memory bloat. There are memory leaks in there somewhere so that the runtime memory footprint slowly ratchets up day after day, and the cpu usage ratchets up too, and eventually I have to restart the app, and occasionally reboot the machine.

Right now, I have SeaMonkey running, but it's just about dead--thread performance is poor once the mem-footprint bloats up, so that keystrokes and mouse-clicks take forever.

Firefox is doing ok at the moment, but not great.

And this is true regardless of whether it's on Windows or Mac.

Now of course it could be that it's a 3rd-party plugin, no way for me to know, but since it happens on both machines, that seems less likely.

I still think IE is dangerous. So I just about never use that. "Edge" not yet clear...

I might be checking out Chrome soon...

The key problem about punting SeaMonkey is what I use to read email after that?

Later: OK, the problem with Mozilla-origin tools is EBay. It might be other websites, too, but EBay is extra bad, whatever they are doing causes the bloat, which means things are tickling the memory-leak bugs. It mostly seems to be motion-oriented, i.e., Flash, movies, animated GIFs, whatever that sort of thing is. Tis as though there's a background thread that runs them, and it fails to properly de-allocate RAM when you leave a page open with these things--they run forever. I'm not even convinced that closing the page would be enough--like that bg thread keeps on going. Later: gad. tis worse than I thought. You'd think each page/tab would have its own lightweight process, but that's not the case.

Also later: been using Chrome at work. Stinks. Later later: well, it has an interesting feature about letting you see what each individual page is up to. And guess what--each page/tab has its own lightweight process, which is why you can see what each page is up to. That's nice. What I think stinks about Chrome is the complete lack of menubar options that Firefox has.

----

MUCH Later (12/26/17): Firefox 45 (yeah, I know, old, but it's that last version that installs on Mountain Lion (upgrade from 8 to 10 coming ~3 weeks) tells me:

"Firefox is now twice as fast, 30% leaner than Chrome and more awesome by a factor of 87 (it's all very technical). Upgrade today!"

Hm. Well, I still have to use Firefox some, because Mtn Lion Safari has issues about SSL with some websites (not sure what, the messages are fairly limited). Occasionally its just timing, I think.

I keep hoping that they are going to discover these memory leaks and resolve them. Faster than Chrome would be better, but likely invisible on my satellite internet service. I need to memory problems to go away.

1:87 is HO scale.

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);

    }
}