|
|
||
Evan Summers's BlogOctober 2006 ArchivesBest linux podcast of the yearPosted by evanx on October 31, 2006 at 06:26 PM | Permalink | Comments (2)I've never actually heard Mr Slackware before (i mean heard him speak), only heard about him since forever of course. Check out this podcast - he's SUCH a cool dude, and so great to listen to - isn't it great that we're in the podcast era?! :) You know what? There's no substitute for hearing a person talk. They say a picture speaks a thousand words. Well then a voice speaks a thousand pictures, innit. C'mon, let's all podcast! Hey, can we upload podcast MP3's to java.net? Cos they can be quite large, like 30Mb!?
Gonna have to add him to my hero list :)
Seek and Deploy 2: The Cookie Jar, RefilledPosted by evanx on October 31, 2006 at 05:08 PM | Permalink | Comments (10)
Actually there is a copy of the "article" here, and the project page is cookiejar.dev.java.net. So we embed a jar within a jar so that we can wrap a pack200 jar in a real jar. But then the wrapper gets to be like a "splash jar" ie. a splash screen written in java, so we got carte blanche, sweeet! In this demo, the splash jar uses URLClassLoader to load the "real" jars. We have one embedded jar (containing demo's as in the menu below), and we also preload an external jar eg. the last item in the menu below. It's disabled until that jar is loaded (off jroller.com). Then that menu item becomes enabled, and you can launch it.
In order to use URLClassLoader for this twisted purpose, we embed a webserver, which is what makes this exercise real fun :) The code is in webservlet/src. You can of course run it by checking out the webservlet.dev.java.net project and running the main class cookiejar.CookieJarLoader. Here is the "raw" CookieJar, ie. a jar file that your browser might be willing to download and run for you with one click?
The CookieJar pops up the above JFrame as soon as your browser finishes downloading it, and decides to run it using java, woohoo! If your browser doesn't run jars, then here is the JNLP of the above jar.
No security... Goddamn, i wish i knew how to sandbox stuff properly?! Um, er... so what is this 150k jar? Well... it contains a few things. (1) an article, (2) another jar with my old boring dinky demos in it, and (3) a webserver, to serve up (1) and (2).
I must confess, i had a huge amount of fun putting this
twisted demo together! :)
The Wrong AnswersPosted by evanx on October 30, 2006 at 01:04 PM | Permalink | Comments (4)Frank Somerville posted "My Favorite Java Developer interview questions" which i enjoyed reading a minute ago. Here are some answers to avoid ;)
Pick one and answer it, correctly or incorrectly :) Any other good questions?
A Tale of Two CDsPosted by evanx on October 30, 2006 at 06:20 AM | Permalink | Comments (5)
Purpose
I got a PC that i wanna setup as a multimedia center. I got a CentOS DVD, but that doesn't have multimedia, so that's how this exercise started. So i was looking for some linux warez. With the following requirements.
2. It must be desktop/multimedia focussed, with either the essential non-free stuff pre-installed, or otherwise easy to install after the fact, eg. with a couple of clicks and no command-line. I'm talking video drivers, MP3 codecs, flash and the like.
3. It must look good. The fonts and colors and icons. I can boast that i'm as shallow as the next discerning consumer, i mean if it doesn't look and feel good, it can't be good. Period.
Clickability
I was using Dapper in Johannesburg, in the sense that my brother's TV setup is a Dapper Drake box. And darn, it looks and feels better than my XP notebook. But it wasn't an easy "clickable offense" to configure that box with accelerated drivers and such. The Synaptic software installer is heaven for opensource stuff. And Yum and the rest. (It makes Windows EXE's look like 1980s DOS technology. Which of course it is.) So why not leverage Synaptic for the other really essential desktop stuff like nvideo drivers, Flash, MP3, as well as Frozen Bubble? I mean out the box, without having to Google-trawl the Tips and Tricks sites, and drop into the command-line to reconfigure repo config files!? It makes me wanna say to the linux vendors, "Dammit to hell, aint ya a dumb ostrich! No offense. It's just that you seem to have stuck your head in the sand like an ostrich. No offense." ;)
But i read that Canonical were gonna provide a non-free repo. Now there we go! They started with Java and got Real. "Excellent, Smithers." Negotiate distribution rights with the nVideo, Flash and MP3 mofo's, and let's get this linux desktop train leaving the frikkin station already!
Test procedure
The acid test is to do what we do everyday. Surf over to YouTube.com, and AllOfMp3.com. And if you get NoTube.com and NoneOfMp3.com, eject the CD, smash it into pieces and throw it in the bin. Darn waste of a perfectly good blank CD.
Results
In a word, um, dismal. Check it out.
RedHat/Fedora - 0/10
A non-starter. Does Fedora have a single live CD coming up? Anyway, Fedora is definitely a non-non-free ostrich, no offense. Fedora will on principle never make it easy to install non-free software. So that's easy, i'll never use Red Hat or Fedora again. Pity because i was a Red Hat man for many years, ie. from 1998 to first half 2006.
SuSE - 0/10
A non-starter because they don't have a single live installable CD on the Novell website (which i found thanks to Google)? Pity cos i hear great things in general about SuSe 10, that it's the best desktop, and Banshee rocks.
Edgy Kubuntu - 1/10
Almost a non-starter. It booted but didn't like my nVidia. I get flashing lines. Comment: The installer's partitioner needs some usability attention. I got some, um, data on the spare partition, and i wanna know for sure that it's not gonna format this before i continue. It only confirms this later. By the way, I prefer the Ubuntu browny orangy theme to this blue theme.
Freespire1.0 - 3/10
Pros: Comes with all the non-free stuff, and their click-n-run warehouse is well done. These guys are way ahead of the curve. Eric Raymond will tell ya that. He's on their board, innit. Cons: My microphone doesn't work. How do you record a podcast with no microphone!? It works with the same software (Audacity) under Windows XP, so... Freespire doesn't seem as shiny as it should. I mean the look and feel. They need to up their game in the artwork department. It's not bad, but it's just not up to scratch with latest stuff from Mandriva and Ubuntu. If they matched them, and my mic worked, they'd get the best score today.
MandrivaOne2007 - 4/10
My brother, a professional linux junkie, phoned me up, and said, "Listen to me for once. Download and install MandrivaOne right now, you stupid donkey! No offense." So i downloaded the latest orangy-themed one, not the beta2 blue one you can find screenshots for. Pros: MP3 and RealAudio works. MandrivaOne's look and feel is great. It trounces Freespire. It rivals Dapper. I used Mandrake 10 for six months at work, in the past year, and it looked like crap. Obviously those Brazillian honeys they shacked up with, have taught them a few new, um, tricks. It's got a 3D desktop too. But how do you enable it? Cos i wanna check out those wobbly windows! Cons: No accelerated video for me. Flash doesn't work out the box. You gotta download that tarball from Adoobie and get medieval with the command-line. Like pretty much all linux distros. Not good enough, fails the mainstream-readiness test right there.
Their package manager is RpmDrake. Didn't Connectiva innovate in this space, using
apt for RPMs? Anyway, i couldn't find Audacity using this tool.
Notwithstanding that, this gets the highest
score today, cos it looks so good.
Now if Ubuntu liked my nVidia card...
Conclusions
The latest linux desktops certainly look like a hot babe, and while they can definitely steam up any server room, don't expect any flash, vibes or accelerated video on the first, um, install. Why can't the linux vendors put an icon on the desktop which says "Click here to activate non-free repository and choose some desktop warez to install at your own risk to our software freedom, and at potential legal risk depending on your locale, and we'll slapt get them from a server in a liberal country and install them for you in a jiffy while you wait, how does that sound?" They are arrogant squanderers, no offence. "Stupid Flanders." What i'm saying is, make it a "clickable offense" to install essential non-free drivers, codecs and what-not. What part of "no command-line" can't they understand?! Maybe 2008 will be the year of the Linux desktop. More likely 2009. Because clearly 2007 is gonna be the year of Vista marketing, hype and excitement. Actually every year is the year of the Linux desktop, not least because it improves pretty drastically every year, and that seems to have accelerated this past year. For me, 2006 was the year that the Linux desktop got better looking than Windows. And went 3D too, innit. Notwithstanding its mainstream-unreadiness, i think any large company that doesn't give most of their users a centrally-managed Linux/OpenOffice desktop today, is just being silly. For one thing, having YouTube et al not working becomes a feature, not a bug. Productivity will go through the roof! ;) If you are cutting a hundred or a thousand identical desktops, then it's worth struggling for a few days to get whatever you need working and nailed down on linux. We know everything does work on Linux - just check MythTV for the multimedia stuff for example.
But if you are installing one desktop at home in your valuable spare weekend time, then who needs the hassle? I mean if the vendors haven't bothered to make it easy to install the essential desktop goodies yet, should we waste our time now, or just wait for the next Ubuntu or Mandriva to get it right, and switch then? In the meantime, buy a MacMini - got root, got YouTube :)
Cute and Pasty: Browser OpenerPosted by evanx on October 29, 2006 at 11:58 AM | Permalink | Comments (9)I'll maintain this page at this permalink. Here's one way to popup a browser (in Windows), or otherwise prompt the user with the URL, ready for cutting and pasting into your browser.
public class BrowserOpener { public void showDocument(String url) { if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) { try { Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); return; } catch (IOException e) { e.printStackTrace(); } } showDocumentRequest(url); } protected void showDocumentRequest(String url) { JTextField textField = new JTextField(url); final JDialog dialog = new JDialog((JFrame) null, "Browser", true); dialog.setLayout(new GridBagLayout()); JPanel panel = new JPanel(new GridBagLayout()); String message = "Please paste the following URL into your browser. " + "Press Control X to cut."; panel.add(new JLabel(message), Gbc.xyi(0, 0, 2)); panel.add(textField, Gbc.xyi(0, 1, 2).horizontal()); dialog.add(panel, Gbc.xyi(0, 0, 4).horizontal()); dialog.setBounds(100, 200, 700, 250); dialog.pack(); dialog.setMinimumSize(new Dimension(600, 40)); textField.setSelectionEnd(url.length()); textField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { } public void insertUpdate(DocumentEvent e) { } public void removeUpdate(DocumentEvent e) { dialog.dispose(); } }); dialog.setVisible(true); } public static void main(String[] args) { new BrowserOpener().showDocumentRequest("http://aptframework.dev.java.net"); System.exit(1); } } This uses gridbaglady.dev.java.net ie. Gbc.java. The following dialog will popup. We already select the URL text, so the user need just press Control X, and it will dispose, thanks to the DocumentListener.
If you
got some ideas on improving this, eg. for other OSes, and other approaches eg. JNLP's
BasicService, please comment, and i'll extend this page. How do you do it?
It Is ThemPosted by evanx on October 28, 2006 at 09:43 PM | Permalink | Comments (2)
Of my heroes, i've seen some of them in real life. In order of appearance, Bill Joy, Eric Raymond, and Richard Stallman. I've emailed a few heroes as well, a few years ago - and they even replied, a few times! Like Bjarne and Alex - the C++ guys. I'm not on first-name-terms with these guys - it's just that i can't remember and/or certainly can't spell their surnames right now! They are very special technologists and academians and people, as i have witnessed. That they deigned to chat to some arbitrary geek on the other side of the world (me), really impressed me, and still impresses me, when i think of it. Bill Joy i saw on a stage at JavaOne only, but it counts! The man that started it all. A living legend. From BSD to Java. The softly-spoken giant amongst us. No, not in the "tall" sense! At least that's how i remember him (being softly spoken). Years ago i saw an email from Mr Joy, where everything was in lowercase, and i've imitated him ever since. He is my original hero! I shouldn't meet him, because then i'll realise he is just a human being. D'Oh! Hey you young 'uns! And if any of you young whippersnappers are wondering why i switch to "Mr," listen up! The best advice i can give to you, is the exact same advice you would've got two thousand years ago from the Romans, two hundred years ago from the English, and are getting today from an aging African (me)... which is, don't swear on the frikkin school bus! Actually that's not it. The best advice i can give you is... "Greet on your feet"! But since you aren't gonna take my advice, you know what? Shuddup! ;) Light and Dark Mr Raymond and Mr Stallman visited Cape Town, so i met them for real. I enjoyed Mr Raymond the most, i mean he is the quintessential "light-being," a fountain of light, a well of knowledge. When you are around him, it's like someone turned the sunlight up. Mr Stallman is the original icon. But he is worried and tense, that the sun is gonna darken. I'm also worried about that, so i like being around people that make me believe that heaven on earth is a done deal, like Mr Raymond. That doesn't mean i don't respect Mr Stallman more or less than Mr Raymond. They uphold the spectrum that refracts light into our lives from different angles. They crystalise our view of the world. Beginnings To complete my journey of heroes, I need to meet Mr Kernigan and Mr Richie. And you know what, i just decided i'm gonna darn well try to! I'm gonna plan a whirlwind tour, where the check-list includes campuses where i might bump into Messrs Kernigan and Richie, and Messrs Knuth and Lamport. Cos in addition to C, I consider LaTeX to be monumental. I can't explain why. Ok, lemme try. My first introduction to tremendously useful, technically awesome, free and open software, was LaTeX. Running on DOS - D'oh!
So here's to our heroes. What would we be without them!? But enough about me - who are your heroes, and why?
Sync and Destroy 1: The Cookie JarPosted by evanx on October 27, 2006 at 04:05 PM | Permalink | Comments (4)
I had so much fun today with this, it shouldn't be legal... Here is a demo which is a jar which hosts embedded demos which are loaded via URLClassLoader looped back to a builtin webserver, also hosting the article that explains it all... Total download size? 115k! (By the time you read this maybe more, but then it'll have more features :) The code is on webservlet.dev.java.net. The next step is to try a nice splash screen, or interactive startup page, and/or login panel, to keep the user distracted while loading a larger application in the background (using URLClassLoader). Address Form with GbcPosted by evanx on October 26, 2006 at 09:21 PM | Permalink | Comments (3)Here's having a go at John's address form using gridbaglady.dev.java.net. Here is the jumble of code...
public class AddressFormPanel extends JPanel { JTextField firstName = new JTextField(); JTextField lastName = new JTextField(); JTextField phone = new JTextField(); JTextField email = new JTextField(); JTextField address1 = new JTextField(); JTextField address2 = new JTextField(); JTextField city = new JTextField(); JTextField state = new JTextField(); JTextField postalCode = new JTextField(); JTextField country = new JTextField(); JButton newButton = new JButton("New"); JButton deleteButton = new JButton("Delete"); JButton editButton = new JButton("Edit"); JButton saveButton = new JButton("Save"); JButton cancelButton = new JButton("Cancel"); JList selectionList = new JList(); JSplitPane splitPane = new JSplitPane(); public AddressFormPanel() { super(new GridBagLayout()); JPanel formPanel = new JPanel(new GridBagLayout()); formPanel.add(new JLabel("Last Name"), Gbc.xyi(1, 0, 2).east()); formPanel.add(lastName, Gbc.xyi(2, 0, 2).horizontal()); formPanel.add(new JLabel("First Name"), Gbc.xyi(3, 0, 2)); formPanel.add(firstName, Gbc.xyi(4, 0, 2).horizontal()); formPanel.add(new JLabel("Phone"), Gbc.xyi(1, 1, 2).east()); formPanel.add(phone, Gbc.xyi(2, 1, 2).horizontal()); JPanel emailPanel = new JPanel(new GridBagLayout()); emailPanel.add(new JLabel("Email"), Gbc.xyi(1, 0, 2)); emailPanel.add(email, Gbc.xyi(2, 0, 2).horizontal()); formPanel.add(emailPanel, Gbc.xyi(3, 1, 0).gridwidth(2).horizontal()); formPanel.add(new JLabel("Address 1"), Gbc.xyi(1, 2, 2).east()); formPanel.add(address1, Gbc.xyi(2 , 2, 2).gridwidth(3).horizontal()); formPanel.add(new JLabel("Address 2"), Gbc.xyi(1, 3, 2).east()); formPanel.add(address2, Gbc.xyi(2, 3, 2).gridwidth(3).horizontal()); formPanel.add(new JLabel("City"), Gbc.xyi(1, 4, 2).east()); formPanel.add(city, Gbc.xyi(2, 4, 2).horizontal()); formPanel.add(new JLabel("State"), Gbc.xyi(1, 5, 2).east()); formPanel.add(state, Gbc.xyi(2, 5, 2).horizontal()); formPanel.add(new JLabel("Postal Code"), Gbc.xyi(3, 5, 2).east()); formPanel.add(postalCode, Gbc.xyi(4, 5, 2).horizontal()); formPanel.add(new JLabel("Country"), Gbc.xyi(1, 6, 2).east()); formPanel.add(country, Gbc.xyi(2, 6, 2).horizontal()); JPanel buttonPanel = new JPanel(new GridBagLayout()); buttonPanel.add(newButton, Gbc.xyi(1, 0, 2)); buttonPanel.add(deleteButton, Gbc.xyi(2, 0, 2)); buttonPanel.add(editButton, Gbc.xyi(3, 0, 2)); buttonPanel.add(saveButton, Gbc.xyi(4, 0, 2)); buttonPanel.add(cancelButton, Gbc.xyi(5, 0, 2)); JScrollPane scrollPane = new JScrollPane(selectionList); JPanel leftPanel = new JPanel(new GridBagLayout()); JPanel rightPanel = new JPanel(new GridBagLayout()); leftPanel.add(scrollPane, Gbc.xyi(0, 0, 4).both()); rightPanel.add(formPanel, Gbc.xyi(0, 0, 4).horizontal().north()); rightPanel.add(buttonPanel, Gbc.xyi(0, 1, 4).south().top(8)); rightPanel.add(new JPanel(), Gbc.xyi(0, 2, 0).vertical()); splitPane.setLeftComponent(leftPanel); splitPane.setRightComponent(rightPanel); splitPane.setDividerLocation(100); add(splitPane, Gbc.xyi(0, 0, 2).both()); } } where Gbc.xyi() is a static convenience method with parameters gridx, gridy and an "inset" for all round. Yes, it is like the Matrix. It's easy though because you got auto-completion, error highlighting et al, in your IDE, which you don't have if you were to edit this as an XML thingy. But it's probably easier and faster to use a GUI builder. Anyway, the more you use Gbc, the more second-nature it becomes - like with anything. Here's the picture...
And a WebStart....
I find i gotta stick to one layout manager (the Matrix) otherwise i get very confused, very quickly.
To simplify layout, i reckon its best to have as many subpanels as possible. And a "spacer panel" or two is
usually called for, certainly with Gbc.
Pulp Diction 2: Top Ten Reasons to Choose Java Over C/C++Posted by evanx on October 25, 2006 at 04:53 PM | Permalink | Comments (19)
I really am so IRRITATED by now with all these Top Ten bookmarks, Goddamn! But if you can't squish 'em like a bug then join 'em like a feature?! "Be the change you want to see in the world." I tried that, doesn't work! Heh heh So I'll start with C++ and then move on to, um, er, whatsit? The other C... No, not Objective-C! The C shh... um... oh frell it, i can't remember! OK, this is a easy one, i mean C/C++ vs Java, it's not even fair comparing a modern language over a legacy one. Actually compared to FORTRAN and BASIC i think C/C++ is FANTASTIC ;) Actually, to be honest, C is my first love, and if it wasn't for C, NOTHING would even exist, so... We owe so much to so few - Dennis, Brian, Bjarne that certainly includes you, dude! Notwithstanding that, here are my Top Ten Reasons Why to Choose Java over C/C++.
So what's your number one reason? Enhanced DTs Extract 1: Progress Dialog Worker PreviewPosted by evanx on October 25, 2006 at 09:28 AM | Permalink | Comments (3)
So anyway, this blog is an extract which is a preview - can you get any more noncommital than that?! extract().start() In addition to the standard SwingWorker we know and love, other convenient workers built on this are popping up, eg. BackgroundWorker in "Swing and Non Blocking JAX-WS", and Task in Hans Muller's JavaOne2006 JSR296 slides. Richard Bair's Web Swinging presents SwingX-WS, a beautiful and convenient API for Swing clients to access webservices - and plain old websites, eg. to do some screen scraping, data mining, or whatever. If you haven't read that article yet, drop everything right now! Seriously! They have even implemented XmlHttpRequest for Swing clients, with an onReady listener. It's seriously awesome. When accessing resources and services over the network, especially the internet, but actually any network, then background threading becomes a necessity, ie. using SwingWorker. One can automate this, eg. SwingX-WS will send the XmlHttpRequest in a background worker, which later fires an onReadyState event to be PropertyChangeListener, in the EDT. But while our long tasks are running, what should we be doing, or more likely not allowing? For instance, the button that launched that task should probably be disabled. Because for example, hitting the "Get New Messages" button repeatedly in rapid succession doesn't really speed up the process of getting new messages from the email server (apparently). Maybe we want to block the whole application, or at least the current panel, eg. using a modal dialog, or glass pane, while that task is executing. We might want to support a progress bar, and a cancel button. (See Kirill's great looking "progress glasspane" on flamingo.dev.java.net.) So maybe we want some enchanced SwingWorker's at our disposal to drop into our code, as in the following example, which uses an enhanced worker of the blocking (modal) dialog variety, with a progress bar built-in for added convenience. protected void refresh() {
refreshButton.setEnabled(false);
try {
DProgressWorker<ListModel, String> worker = new DProgressWorker(
new DDefaultProgressDialog(frame, "Simulating long task", true)) {
protected Object doInBackground() throws Exception {
for (int i = 50; i < 100; i++) {
taskProgress(i);
if (i%10 == 1) taskInfo(Level.INFO, "Mmmmm.... " + i + "st donut");
Thread.sleep(50);
}
return new DefaultListModel();
}
};
ListModel listModel = worker.get();
} catch (DCancelledException e) {
logger.warning(e);
} catch (Throwable e) {
dialogHelper.showExceptionDialog(e, null);
} finally {
refreshButton.setEnabled(true);
}
}
}
I'll leave the blow-by-blow discussion of its implementation for a follow-up article, but here is a sneak preview...
You'll see that we introduce a "task listener" with which we construct our "progress worker." The worker publishes its current progress to the task listener, which in this example is a dialog with a progress bar and what-not. Here is a screenshot of the demo running.
The next part of this "mini-series" will look at using this progress worker in a nonblocking fashion, where it publishes the progress to a status "mini-bar" at the bottom of the frame. postscript() Incidently i've been evolving a GUI "framework" which i'm using for these WebStart demos, and writing an article called GooeyBeans, with some beans binding and configuration and what-not. I did a bunch of stuff yesterday till all hours which got me to waking up this morning realising that so much of that software and article is actually rubbish. Gutting. But i am happy that it's gonna be much better than it is. That's what R&D is all about, making rubbish so that you don't make rubbish. So i've renamed that article to "Squashed Gooey Beans," which is like, squashed and deprecrated, and not gonna be "published." It's on quitegooey.dev.java.net but please don't read it. I'm gonna rehash it, but hopefully not re-hash it, if you know what i mean. No, not hashish, i mean "making a hash of it." Firstly i'm gonna make it a toolbag of toolkits rather than a "framework." Safer that way. Better that way. More application classes, but less smoke and mirrors aka reflection in the toolkit. That's why i'm happy that i'm breaking up this Enhanced DTs article into a mini-series, because that was also starting to look like an article to be renamed "Squashed DTs" at a later stage.
Funnily enough i realised
last week that quitewriter was rubbish too. Anyway, i've redone
quitewriter as quitehyper.dev.java.net
and now i'm happy with that, very very happy in fact. Cos its so nice an' minimal, like... Mmmmm... crumpled up cookie thingies.
Pulp Diction 1: Top Ten Reasons Why To Work from HomePosted by evanx on October 24, 2006 at 06:17 AM | Permalink | Comments (7)So my top, um, six reasons Why To Work From Home (shortly to be followed by my Top Ten Why Not To) are as follows.
What are other reasons do you think? Then we can grow this list to a Top Ten at least :) Um, some on the more serious side would not be amiss either ;)
Ps. My previous not-so-serious blog was
"Five Steps to Freedom, Almighty Administrator", in case you're a sysadmin?
Trip and Tick 200: WebStart me up, baby!Posted by evanx on October 19, 2006 at 03:31 PM | Permalink | Comments (7)This article will be updated over time at this permalink. So today i tried pack200 - and it compresses my jar from 440k to, urm, 110k! OK, so, um, clearly all WebStart jars should be made this way. Some great resources i found in the process were deployment.dev.java.net which has the important links related to WebStart, and "Deployment Tips and Tricks using WebStart and Java Plugin" (JavaOne2006 PDF slides), which shows you how to lazily load your app via the web, eg. maybe your WebStart app is just a login splash-screen teaser thingymajig that loads your real app in the background, to create the perception of loading a very large app, very quickly. "Mmmm... crumbled-up cookie things." Previously in "JooJ up your project page with a WebStart demo" we looked at using Netbeans' JNLP tool to create our first JNLP file. This article is the PG13 SNVL version of that, minus the sxx, nudity, and violence - darn it to hell, what a frikkin pity! At least we got the bad language going on, woohoo!
Once we get through the XML with all those angle brackets, we notice that a JNLP file is actually a trivially minimal thing that just specifies our main class, dependent jars, required JRE version, and um, URL thingy.
<jnlp codebase="http://jroller.com/resources/e/evanx/"> <information> <title>QuiteBusy</title> <vendor>aptframework.dev.java.net</vendor> <icon href="default"/> <offline-allowed/> <shortcut online="true"/> </information> <security> <all-permissions/> </security> <resources> <j2se version="1.5+"/> <jar href="aptfoundation610.jar"/> <jar href="cglib.jar"/> <extension href="javadb.jnlp"/> </resources> <application-desc main-class="quitebusydemo.common.QMessageBusDemo"> <argument>logger.level=FINEST</argument> </application-desc> </jnlp> We specify the codebase to be where we upload our jars eg. jroller.com. The all-permissions means we can be naughty, eg. use reflection. Hopefully in future there'll be a some-permissions option. For testing offline, we would specify the codebase as a local directory where we gonna put we jars.
<jnlp codebase="file:///C:/my/jars"> ... </jnlp>
Wrapping Jars signed by others You can test if a jar is signed as follows, using jarsigner, which is included in the JDK of course (along with keytool, pack200 and javaws).
jarsigner -certs -verbose -verify activation.jar If any of your resources are jars signed by others, you gotta wrap those in a JNLP as follows, and put this in the resources section of the JNLP as an extension, as shown above.
<jnlp codebase="http://jroller.com/resources/e/evanx/" href="javadb.jnlp"> <information> <title>JavaDB jar</title> <vendor>Signed by Sun Microsystems, Inc</vendor> <offline-allowed/> </information> <resources> <jar href="derby.jar"/> </resources> <component-desc/> </jnlp>
We create a keystore (file), and a key therein, using keytool (in the JDK).
cd /my/jars keytool -genkey -keystore myKeystore -alias myself keytool -selfcert -alias myself -keystore myKeystore keytool -list -keystore myKeystore ls myKeystore
We copy our minty jar and sign it as follows.
cd /my/jars cp /my/nbprojects/myproject/dist/my.jar . jarsigner -keystore myKeystore my.jar myself pack200 my.jar.pack.gz my.jar javaws my_local.jnlp pack200 creates my.jar.pack.gz, where we need to have both the original jar, as well as the compressed gz one, in our codebase. The heffalump one is a fallback, eg. for older javaws clients that don't do the pack200 thing. I keep two JNLP files, one local one (with my local jars directory as the codebase) eg. my_local.jnlp, and then the uploadable one eg. my.jnlp, with the online codebase, eg. on jroller.com. In the above script, i test using the local one, before uploading and testing the online one.
Now we can insert the JNLP link into our HTML web page as follows.
<a href="http://jroller.com/resources/e/evanx/messagebus610.jnlp"> <img border="0" src="http://javadesktop.org/javanet_images/webstart.small.gif" alt="Launch"/></a> <tt>(QuiteBusy, 110k/440k, unsandboxed, Java5)</tt> Which looks like..
We need to put the alt "Launch" text in the img element, because some people configure their browsers to load images from the "originating" site only, in which case the webstart.small.gif won't show, and they'll see nothing to click on. You'll want check the server webserver logs (if you can), to make sure it is the pack200'ed my.jar.pack.gz that is getting downloaded :) Here's a sneak preview of some code-matter i wrote today for an article to be published next week or so, or at least a "trailer" for an article - hey, why do they call it a "trailer" when it comes before?! Anyway it's a SwingWorker thingy with a progress dialog built-in, for added convenience.
Mmmm... JavaPosted by evanx on October 17, 2006 at 11:07 AM | Permalink | Comments (6)I understand a bit more now about closures thanks to great comments in a previous blog entry. For instance, return, break and continue inside the closure, might be used to "transfer control outside the body of the closure" (as said here) ie. in back in the enclosing method. Since such functionality could be mimicked by throwing exceptions, what about using the throw keyword as follows.
void refreshCalendarItems() { try { while (retry-- > 0) { List<CalendarItem> calendarItems = BlockingWorker() { try { calendarItems = connection.getCalendarItems(today, ProgressDisplay(int percent) { publishProgress(percent); }); return calendarItems; // from closure, not from enclosing refreshCalendarItems() } catch (CancellationException e) { publishMessage("OK, so you wanna cancel, fine!"); throw return; // from refreshCalendarItems() } catch (IOException e) { publishMessage("Comms error occurred"); throw continue; // retry } catch (RuntimeException e) { publishMessage("Unrecoverable fatal exception"); throw e; // propogate out of closure } finally { ... } } } ... // update GUI with calendarItems } finally { ... } } where our BlockingWorker is self-starting, and would pop up a modal progress dialog to block while the worker is running. I'll try write such a worker in a later blog article, which will probably use a message bus.
C# has delegates. I like those, eg. for event handler registration. My crystal ball tells me that Java will get popular features of other popular languages. This is a bug and a feature of software, that competing products spur each other on to more "convenience" aka complexity. And languages, toolkits and tools are very competitive, eg. Java vs .NET, RoR, et al. Each release will try to provide more feature candy for developers eg. inspired by popular features in competing languages. "We got closures and delegates too, haa-Haa!"
What i keep banging on about is toolable references to properties and methods. What about a convention like MyClass#updateGui and object#updateGui, representing a class artifact by name, ie. a Field, Method or PropertyDescriptor. Actually first and foremost properties, then methods eg. event handlers, then maybe fields, or maybe not even fields. The important thing is that MyClass#updateGui is understood by IDEs, and so auto-completable, verifiable, refactorable, and all that. The effect could be that the compiler sees MyClass#updateGui as just the String "updateGui", or maybe better, as new ClassArtifact(MyClass.class, "updateGui"). The result is that we can do cool stuff like refactorable, toolable, reflection-safe beans binding, method delegation and what-not as follows.
public class MoviePresentationModel { String title; Integer year; ... public String getTitle() { return title; } @StringValidator(empty = false) public void setTitle(String title) { this.title = title; } ... } public class MoviePresentation { GuiHelper helper = new GuiHelper(this); ... JTextField movieTitle = new JTextField(); ... MoviePresentationModel presentationModel = new MoviePresentationModel() public MoviePresentation() { binder.bind(movieTitle, presentationModel, presentationModel#title); refreshButton.addActionListener(helper.createActionListener(this#refresh)); } @Action(block = Action.WINDOW, worker = BlockingWorker.class) protected void refresh() { ... helper.invokeAndWait(this#updateGui); } @InEdt() protected void updateGui() { ... } }
I like this because i have a little problem with strings as references. Because they aint toolable, verifiable, factorable, refactorable, and worst of all, you gotta type them out letter by letter!
Enum references with no Strings attached I guess we can implement this ourselves as follows.
@PropertyInfo(MoviePresentationModel.class) public enum MoviePresentationModelProperty { title, year, ... } @MethodInfo(MoviePresentation.class) public enum MoviePresentationMethod { refresh, updateGui, ... }
We just gotta make sure that we include these in unit tests, so that when a refactoring operation breaks something, we find out sooner rather than later. Our application might then be factored as follows.
public class MoviePresentation { GuiHelper helper = new GuiHelper(this); ... JTextField movieTitle = new JTextField(); ... protected MoviePresentation() { helper.bind(movieTitle, presentationModel, MoviePresentationModelProperty.title); refreshButton.addActionListener(helper.createActionListener(MoviePresentationMethod.refresh)); } @Action(block = Action.WINDOW, worker = BlockingWorker.class) protected void refresh() { ... helper.invokeAndWait(MoviePresentationMethod.updateGui); } ... } where our helper can use name() to get the name of the enum object, and use that to get the Method or PropertyDescriptor being referenced, since it was already passed a reference to this in its constructor. The problem is that MoviePresentationMethod.updateGui is not navigable, eg. using Alt-G in Netbeans, and that's a showstopper for me, for methods anyway.
Testing Enum Property References Let's say we gonna use enums for property references, rather than strings. To make it safe for refactoring, we need to test, which we can do as follows.
@Test()
public void test() {
try {
testProperty(MoviePresentationModelProperty.class);
... // all our other such classes
} catch (Exception e) {
assertTrue(e.getMessage(), false);
}
}
public void testProperty(Class propertyEnumType) throws Exception {
PropertyInfo propertyInfo =
(PropertyInfo) propertyEnumType.getAnnotation(PropertyInfo.class);
if (propertyInfo == null) {
throw new Exception(propertyEnumType.getSimpleName());
}
test(propertyInfo.value(), propertyEnumType);
}
public void test(Class beanClass, Class propertyEnumType) throws Exception {
BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
List<String> propertyNameList = new ArrayList();
for (PropertyDescriptor descriptor: beanInfo.getPropertyDescriptors()) {
propertyNameList.add(descriptor.getName());
}
for (Enum propertyReference: (Enum[]) propertyEnumType.getEnumConstants()) {
String propertyName = propertyReference.name();
if (!propertyNameList.contains(propertyName)) {
throw new Exception(propertyName);
}
}
}
Mmmm... refactoring.
I wrote this blog using very minimal thingymajig which i wrote just yesterday, which i call quitehyper. I'll write a blog on it a week or so, once i've written a WebStart'able GUI tool to go with it. But here's a sneak preview of its code :) Here's a snippet of the source text (for this article).
What Might We Get //section
Now that i understand a bit more about
:{closures}{http://weblogs.java.net/blog/evanx/archive/2006/10/better_than_goo.html},
i'm a happier and more fulfilled person.
No really. <i>Hopefully no one everytakes anything i say seriously!?</i>
For instance, #return, #break and #continue inside the closure, can refer
to the enclosing method. Since such functionality can only otherwise be mimicked
by throwing exceptions, i suggest that we use the #throw keyword? <i>Unless
someone else already did?</i>
<pre class='java'>
void refreshCalendarItems() {
try {
while (retry-- > 0) {
List<CalendarItem> calendarItems = BlockingWorker() {
try {
calendarItems = connection.getCalendarItems(today,
ProgressDisplay(int percent) {
publishProgress(percent);
});
return calendarItems; // from closure, not from refresh()
...
We automatically do some syntax highlighting (and HTML escaping) of the Java code in pre blocks, as below, as below. (That is not a typo.)
public class NHyperJavaProcessor { static final String keywordStyle = "color: #000099; font-weight: bold"; static final String stringLiteralStyle = "color: #99006b"; static final String numericLiteralStyle = "color: #780000"; static final String methodStyle = "font-weight: bold"; static final String[] delimiters = new String[] { "\n", "... //", "//", "<", ">", " ", ".", "(", ")", "{", "}", "[", "]", "\\\"", "\"", "+", "-", "*", "/", "@", "%%", "%", "#", "package", "import", "static", "final", "abstract", "synchronized", "transient", "default", "class", "interface", "@interface", "enum", "extends", "implements", "public", "private", "protected", "true", "false", "null", "instanceof", "void", "boolean", "int", "char", "long", "throws", "throw", "try", "catch", "finally", "new", "this", "super", "if", "else", "for", "while", "continue", "break", "return" }; enum NState { quoteState, spaceState, dotState, wordState, noState; }; enum NType { wordType, leftRoundBracketType, punctuationType, keywordType, delimiterType, dotType, quoteType, spaceType, leftAngleBracketType, rightAngleBracketType, ampersandType, percentType, noType; }; String sourceText; StringBuilder stringBuilder = new StringBuilder(); StringBuilder subBuilder = new StringBuilder(); NState state = NState.noState; public NHyperJavaProcessor(String sourceText) { this.sourceText = sourceText; } public static String processText(String sourceText) { return new NHyperJavaProcessor(sourceText).process(); } protected String process() { for (String token : NTokeniser.tokeniseText(sourceText, delimiters)) { processToken(token); } stringBuilder.append(subBuilder); return stringBuilder.toString(); } protected void processToken(String token) { NType type = getTokenType(token); token = escape(token, type); if (state == NState.wordState) { if (type == NType.leftRoundBracketType) { stringBuilder.append(spanStyle(methodStyle, subBuilder.toString())); stringBuilder.append(token); subBuilder.setLength(0); state = NState.noState; return; } state = NState.noState; } else if (state == NState.quoteState) { if (type == NType.quoteType) { subBuilder.append(token); stringBuilder.append(spanStyle(stringLiteralStyle, subBuilder.toString())); subBuilder.setLength(0); state = NState.noState; return; } subBuilder.append(token); return; } else if (state == NState.spaceState || state == NState.dotState) { if (type == NType.wordType) { stringBuilder.append(subBuilder); subBuilder.setLength(0); subBuilder.append(token); state = NState.wordState; return; } state = NState.noState; } state = NState.noState; if (type == NType.quoteType) { stringBuilder.append(subBuilder); subBuilder.setLength(0); subBuilder.append(token); state = NState.quoteState; return; } else if (type == NType.spaceType) { state = NState.spaceState; } else if (type == NType.dotType) { state = NState.dotState; } else if (type == NType.keywordType) { token = spanStyle(keywordStyle, token); } stringBuilder.append(subBuilder); stringBuilder.append(token); subBuilder.setLength(0); } protected NType getTokenType(String token) { if (token.equals("\"")) return NType.quoteType; if (token.equals("(")) return NType.leftRoundBracketType; if (token.equals(" ")) return NType.spaceType; if (token.equals("\t")) return NType.spaceType; if (token.equals("\n")) return NType.spaceType; if (token.equals(".")) return NType.dotType; if (token.equals("<")) return NType.leftAngleBracketType; if (token.equals(">")) return NType.rightAngleBracketType; if (token.equals("&")) return NType.ampersandType; if (token.equals("%")) return NType.percentType; if (stringHelper.startsWithLetter(token)) { if (stringHelper.equals(token, delimiters)) { return NType.keywordType; } else { return NType.wordType; } } return NType.noType; } protected String spanStyle(String style, String string) { return formatter.format("<span style='%s'>%s</span>", style, string); } protected String escape(String token, NType type) { if (type == NType.leftAngleBracketType) return "<"; if (type == NType.rightAngleBracketType) return ">"; if (type == NType.ampersandType) return "&"; return token; } }
which is like a state machine or something?
Better than GoodPosted by evanx on October 11, 2006 at 04:11 AM | Permalink | Comments (6)Thanks to comments to The Good, The Bad and The Ugly, i was taught that closures have important features, like access to non final variables in scope. This does simplify code as follows (see Event DTs), where otherwise arguments all have to be final, and variables like option can't be local in showConfirmDialog(). public Class EdtHelper {
...
int option; // not needed, yay
public int showConfirmDialog(String message) {
int option;
invokeAndWait(Runnable() {
option = JOptionPane.showConfirmDialog(null, message, null,
JOptionPane.YES_NO_OPTION);
});
return option;
}
public void showExceptionDialog(Exception e, String message) {
if (message == null) message = "D'oh!";
invokeAndWait(Runnable() {
JOptionPane.showMessageDialog(null,
new String[] {message, e.getMessage()},
null, JOptionPane.OK_OPTION);
});
}
public void setEnabled(Component component, boolean enabled) {
invokeAndWait(Runnable() {
component.setEnabled(enabled);
});
}
public void requestFocusInWindow(Component component) {
SwingUtilities.invokeLater(Runnable() {
component.requestFocusInWindow();
});
}
...
public void invokeAndWait(Runnable runnable) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeAndWait(runnable);
} else {
runnable.run();
}
}
public void setWaitCursor(boolean waitCursor) {
invokeAndWait(Runnable() {
Cursor cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
if (!waitCursor) cursor = null;
applicationFrame.setCursor(cursor);
if (glassPane != null) glassPane.setVisible(waitCursor);
});
}
public void startWorker(Runnable runnable) {
SwingWorker swingWorker = new SwingWorker() {
public Object construct() throws Exception {
setWaitCursor(true);
try {
runnable.run();
} catch (Exception e) {
showExceptionDialog(e, null);
} finally {
setWaitCursor(false);
}
return null;
}
};
swingWorker.start();
}
}
Then starting long tasks using above EDT-agnostic support methods would be quite neat, and we can use non final variables in scope, eg. taskDescription. refreshButton.addActionListener(ActionListener(ActionEvent e) {
String taskDescription = "Refresh data from server";
edtHelper.startWorker(Runnable() {
try {
... // long task
if (!edtHelper.showConfirmDialog(
"Oops, timeout! You wanna try another server?")) {
return;
}
...
} catch (Exception e) {
edtHelper.showExceptionDialog(e, taskDescription);
}
});
}
}
So potentially very nice and useful.
But if closures can have access to
non final variables in scope, then why can't anonymous classes just get them first?
I mean, is that all closures offer compared to anonymous classes?
That is apart from saving keystrokes, which i don't think is terribly important,
because IDEs type those for us anyway, as argued in my original blog -
and that arguably it's harmful because it reduces clarity as to the relevant method,
and provides two syntaxes for the same thing.
The Good, The Bad, and The UglyPosted by evanx on October 10, 2006 at 07:55 AM | Permalink | Comments (5)In Concise Instance Creation Expressions: Closures without Complexity by Bob Lee, Doug Lea, and Josh Bloch, they propose the following syntax for closures, as a shorthand notation for anonymous classes with a single abstract method type, eg. Runnable, Comparator and the like. List<String> stringList = ... ;
Collections.sort(stringList,
Comparator<String>(String s1, String s2) {
return s1.length() - s2.length();
}
);
At first i thought why not include the method name as follows, for clarity. Collections.sort(stringList,
Comparator<String>.compareTo(String s1, String s2) {
return s1.length() - s2.length();
}
);
Then i remembered, i just prefer the old familiar anonymous classes, and i'm not much interested in shorthand. Take abbreviations for instance. Abbreviation is the Clarity-Killer, which passes through you and all that remains is Confusion. Programmers, that don't use IDEs, are not lazy enough! And inconsiderate. They leave a trail of broken windows like abbreviations and inconsistent names, and code in need of refactoring, which they can't fix - because they don't use IDEs! That's why not using IDEs is the root of all evil ;) Anyway enough of that nonsense. Let's consider Swing event handlers. okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
okActionPerformed();
}
});
The following shorthand would avoid the method declaration. okButton.addActionListener(new ActionListener().actionPerformed(ActionEvent e) {
okActionPerformed();
});
And the following is as shorthandish as one could possibly make it, as in the above-mentioned proposal. okButton.addActionListener(ActionListener(ActionEvent e) {
okActionPerformed();
});
Considering that in Matisse, Netbeans generates and hides this code, and in general IDEs
generate anonymous class methods for you, to save those keystrokes, is there really a need
for such a shorthand notation? It goes towards having multiple
ways to do the same thing (The Bad), and towards less keystrokes for IDEA-less Clarity Killers
(The Ugly), and anyway what's wrong with anonymous classes (The Good, The Verbose and The Clear)?
| ||
|
|