Apr 2, 2010
*tap tap tap* Is this thing on ?
Der Plan – Part one
I’d like to present another piece of the big UI puzzle I’m slowly putting together. Shizzle is a query engine for Java and Swing that supports the CSS selector syntax.
It started one or two years ago as a straight port of Sizzle (jQuery and other libs’ selector engine) to Java. More for fun than, say, because it’s a clever idea: it just isn’t.
For fun, at some point I also started adding more selectors to Ben Galbraith and Dion Almaer’s Clarity. In the end, when this became something I wanted to complete, I restarted from scratch, TDD style.
Querying allows you to remove member variables for inner components you rarely use. Just look at a Matisse-generated panel to see what I’m talking about.
Shizzle shines used with composites (which I’ve already talked about), and APIs tailored for chaining. Allowing you to have really concise, simple and expressive code, obviously very similar to what you do in jQuery. You’ll see some of this in the download, because I used it in the tests, but the main part has been removed so we have something to talk about next time!
How to use it
We have a lot to talk about, most of CSS selectors are supported. The ones that aren’t follow the same rationale as Sizzle, they’re just not useful. However, I sometimes went overboard and implemented some features and selectors that people will probably never use.
You use it like this. Create a Query like this new Query (“my awesome query”), and call the matches() method with the containers you want to look into. That’s it.
I’ll quickly describe what Shizzle supports, if you want more comprehensive (and normative) explanations, the official W3C spec can be found at http://www.w3.org/TR/css3-selectors.
0 – Universal selector
“*” matches everything!
1 – Type selectors
A query “Type” will match every Java object of the Type class. Note that it works on classes’ short names (ie, no package) and that it does not take inheritance into account. It just checks if an object’s class has the same name, so “JComponent” won’t match JPanels and so on. Common examples include “JPanel” “JButton” “JLabel” and so on.
2 – Id selectors
JComponents can have a name, and this selector targets it. “#name” will match every component that has been named “name”. Since there can be more than one component with the same name, this query can return more than one result (which is not the case with HTML). In practice, it doesn’t change anything.
3 – Style classes
HTML has the concept of classes, a multivalued (usually space-separated) property that labels an element with a string, which you then style using CSS. Here this concept uses a client property (“HTk.style”) to store the class values (which I’ve dubbed style classes, because just talking about classes would be confusing). The query “.style” will match every component that has our client property where one of the style classes is “style”. As you’ll see in the code, HTk helps in adding, removing, and getting the value of this style property. You won’t have to manage it manually, don’t worry.
4 – Attribute selectors
These selectors involve properties (getters only) and their value for matching purposes. This one is rather complex as you can check whether a property exists or not, check its value or compare it, and so on.
The query “[property]” will match every object that has a readable property named “property” (ie a “getProperty” method). I’m guessing you won’t be using it that much.
Values can be checked too, like this “[name=id]“. Which show how we could describe the id selectors we talked about earlier, as attribute selectors checking the value of the “name” property, using the “getName” method on each component. The “=” is the first of many operators, checking of course the property has exactly the specified value.
There are some microsyntaxes and types are “automatically” converted, for that purpose I’ve built a class for conversion. I’ve followed Tim Boudreau’s pragmatic rules about it (Dozer or Morph being way too complicated for what I needed here). You can compare floats to double and Strings to ints, and so on. Strings can be defined with single or double quotes, or, as just demonstrated, without quotes at all. You’ll see this in more (excruciating) detail in the unit tests.
[Aside: I've also included one example converter for Dates (eg "2009.12.01 12:45:12") , and a more realistic one converting colors (accepting diverse formats such as the hex style "#FF0000" "#FF0000FF" "0xFF0000" "0xFF0000FF" "#F00", constants in the Color class such as "red" or "RED" or "Color.red", and finally the ones we often see in CSS like "rgb(255,0,0)", "rgba (255, 0, 0, 255)", "Color.rgb (255, 0, 0)" or "Color.rgba (255, 0, 0, 255)" variations).
I'm releasing this conversion "framework" (such a big word for one class) under the WTFPL, the Do Whatever The Fuck You Want Public License, in hopes we'll stop having so many of them]
Common examples would be “[visible= false]” (notice the boolean conversion) or “[background=blue]” (using the Color conversion we just talked about)
The next operators involve checking the value of a String property. “^=” checks whether the String property starts with the specified value, “$=” whether the property ends with the value, “*=” whether the property contains the value, “~=” whether the property has one word being the value, “|=” is used with hyphen-separated values.
Now we’ll leave the regular CSS world, and go into the ease of use world. There’s no “different” or “greater than” operator, to negate tests, they use a special pseudoclass, which we’ll see later. I’ve decided to add “!=” “>” “<” “>=” “<=“, and these special Java ones “==” and “!==” (the “=” ending up using equals, where “==” is the “same as” operator and uses the regular == in Java. It happens this is not that useful by the way, it’s one of those times where I’ve already said I went overboard). The comparison operators use the Comparable interface, so you’ll be able to make queries with comparisons for any class that implements it.
Common examples are checking the bounds or location of a component “[x<=10.5]” and the likes.
While I was at it, I decided to throw in support for client properties here. So while we’ve been talking about attribute selectors for properties, know that the same applies to client properties. “[foo=bar]” will match any component that has a “foo” client property with a “bar” value.
5 – Pseudo classes
Pseudoclasses are usually used to check some specific state. While there are some predefined states in HTML/CSS such as “:checked” “:enabled“, there aren’t any Swing specific yet. What I did is add support for boolean properties as pseudoclasses. “:visible” is equivalent to “[visible=true]” and thus, will match any component whose isVisible() method returns true.
There’s also other selectors using the pseudoclasses syntax, but they have their own semantics, so they really are apart.
6 – Multiple selectors
Now that we’ve seen the most common selectors it’s time to talk about the fact that they can be combined. “JPanel#details” combining a type selector for JPanels and an id selector for the “details” name. Depending on the selector you could have some of the same type. Other examples could be “JPanel#link[visible=false]“, “[x=10][y=20][width=30][height=40]“, “[name=id][opaque=false]“, “.style.style2” and so on.
7 – Position selectors
Position selectors involve checking the index of the component, and allows you, when combined with other selectors, to ask for the first element (“:first“), like the first JPanel of the UI (“JPanel:first“), or the last one (“:last“), or the 5th one (“:eq(5)” or “:nth(5)“) or every other one (“:even” and “:odd“), or why not the ones after the 4th element (“:gt(4)“) or before it (“:lt(4)“), or all the ones between two values (“:range(2, 5)“). Those are coming from jQuery, they don’t exist in CSS.
8 – Structural pseudo classes
Similar to position selectors, structural pseudo classes are a little richer, as they apply to elements in regards to their parents, the number of other children they have, etc.
“:first-child” and “:last-child” will only match components that are the first and last child of their parents.
“:only-child” matches only elements that don’t have any siblings.
“:nth-child()” is used for matching the index, like these simple selectors “:nth-child(1)” or “:nth-child(odd)” or “:nth-child(even)“. It’s also possible to use a 2n+a formula: “:nth-child(2n)” are all the children with an even index, “:nth-child(2n+1)” match the odd ones, “:nth-child(-n+2)” matches the first 2 children, “:nth-child(3n)” matches every 3rd child. Look at the tests to see some more concrete examples.
9 – Special pseudoclasses
There are two other pseudoclasses, namely “:has” and “:not“.
“:has()” helps you match components having children matching a specific rule, for example “:has(JPanel)” matches any component that has a child that matches the “JPanel” rule, any container having at least a JPanel child will match this rule.
The “:not()” reverses results of a specific rule, for example “:not(JPanel)” will match any component that is not a JPanel. In CSS it’s interesting to ahve because there’s only affirmative checking on attributes for instance. There’s some specifics in CSS, you can’t have a complex selector inside, like another :not. I decided not to bother with these, so you can go wild and nest 15 :not if you want.
10 – Combinators
While you can combine selectors just by adding them together, you can also combine groups of selectors.
The simplest combinator is a space character ” “. It’s the descendant combinator, so you can have components matching a rule, who also have descendants (or parents, depending on how you look at it) matching another rule. “#parent #match” will match any component named “match” that has an ascendant named “parent”.
The child combinator “>” is used for immediate parents and children (whereas descendants and ascendants go anywhere in the component tree). “JPanel > JLabel” will match any label that has a JPanel parent. “JPanel > .style2 > JLabel” will match any JLabel having a parent with the “style2″ style class, which in turn has a parent that is a JPanel.
The adjacent sibling combinator “+” is used to test the component immediately preceding the one being tested. “JLabel + JPanel” will match any JPanel that has a JLabel sibling immediately preceding it in any parent.
The general sibling combinator “~” is similar but applies to any preceding sibling, not just the one immediately preceding the component being tested. “JLabel ~ JPanel” will match any JPanel having a JLabel sibling before him, regardless of its position.
Adding “,” is equivalent to creating another query and doing an union of the two result sets. “JLabel,JPanel” will match JLabels and JPanels. “:not(JLabel,JPanel)” won’t match any JLabel or JPanel. And so on.
Looking at the unit tests you’ll be able to see real Swing panels and components being created and queried (and with a nice “JPanel.HPanel#h1[name^=h] > JPanel:not(.HPanel):nth-child(2) JLabel.HLabel[name^=l][visible!=true][text*=Hello]:only-child:first-child:last-child” query for instance)
Since I ported some of Sizzle’s regular expressions, I’ve included its BSD license. Shizzle itself also is released under BSD, as usual.
This project definitely needs more documentation and tutorials.
Right now, there’s very little error handling at the query syntax level, and definitely needs to be improved.
I’d also probably add a visual tool to test queries on a live UI some time in the future.
The performance could be enhanced, even if it’s good enough for my uses. Right now the whole Swing UI is flattened into a list, which is then checked for matches. The matching could be done while traversing the tree for most of the selectors, so there’s another nice improvement to memory usage that could be done here. The queries could be cached, and modified or invalidated when components are dynamically added, etc
I’m currently looking at scenegraphs, to replace Scenario. So far I’ve “ported” (hacked) Pulpcore to the desktop (removing the dependencies on Applets), and tried Piccolo2d. So expect Shizzle to be ported to one of those, or some other scenegraph, when I find the one I want to use.
Maybe it’d be interesting to port it to other platforms such as Android, or to other toolkits, like SWT or Pivot.
Of course, extensibility for parsing, microsyntaxes, operations and so on, is definitely something that I’ll do too.
Shizzle comes with quite extensive unit tests (which wasn’t that hard, since it was TDD’ed), that show you running examples, and make up a little for the lack of docs. I also “ported” a subset of the official CSS selector spec test suite, with all this I’m confident it works nicely.
You can also look at a subset of HTk, a set of API wrappers which we’ll talk about in more detail in the future, for now let’s just say it contains methods to help set the name, the style classes, etc (the HComponents themselves are not added to the Swing hierarchy, they’re just builders of JComponents).
Here’s the zipped Eclipse project.
I’d love to get feedback on this, you can find me at twitter.com/lqd.