|« July 2004||September 2004 »|
I'm Ryan Lowe, a Software Engineering graduate living in Ottawa, Canada. I like agile software development and Ruby on Rails.
I write this blog in Canadian English and don't use a spell checker. Typos happen.
» Full-time Ruby on Rails freelancer
» Full-time with Rails since May 2005
» Former committer for RadRails (now Aptana)
» I also have a few Rails side-projects in development:
1. wheretogoinTO.com Toronto nightlife
2. Hey Heads Up! TODO list and sharing
3. Layered Genealogy family history research
4. foos for foosball scoring
5. fanconcert for music fans (on hold)
Hiring Rails developers? I can telecommute by the hour from Ottawa, Canada
»» Email: rails AT ryanlowe DOT ca
Now hosted on Hey! Heads Up -- check it out!
Derek Lowe's (Ryan's older brother) words at Ryan's funeral
email@example.com no more
Forging Email Headers: Good, Bad or Ugly?
Sarcastic Dictionary (Part 1 of Many)
Twisting Rails is Risky Business
Risky Business? My Take on Early Alphas
Whoa, it's August 2007
A Postscript to "Growth at the grassroots"
»» All Blog Posts
David Heinemeier Hansson
James Duncan Davidson
Signal vs. Noise
Amy Hoy: (24)slash7
Luis de la Rosa
# Super Size Me
I saw the documentary Super Size Me recently. It's about a guy who decides to eat nothing about McDonalds food for 30 days. At the end of it he had gained 25 pounds, had elevated cholesterol and was seriously damaging his liver.
The documentary also covered generally the obesity problem in the United States -- relating to diet and exercise -- to complement a man's story of destroying his body.
Was this documentary extreme? Yes, but it illustrates a point. Even the man's doctors were surprised about the amount of damage this diet did to him. Even though no one in their right mind -- or without the prospect of a high-grossing documentary -- would eat McDonalds every day, it gives you an idea of how dangerous the food is. It gives doctors a better idea as well.
I stopped eating McDonalds when I went to university for a pretty pragmatic reason: I was downtown Ottawa with no car, the nearest McDonalds was six blocks away and wasn't as good as the one in Renfrew, the town I went to high school. I also had a cafeteria meal plan. Paying for food outside of that didn't make much sense.
High school, right ... did I mention I ate McDonalds every day for an entire school year? Ironically enough, I was sick of the cafeteria food. In retrospect it wasn't too healthy ... but what did I know? I was a seemingly indestructible kid and McDonalds tasted good -- until you got the feeling we affectionately called Post-McDonalds Syndrome. That kicked in about an hour later. I won't get into that...
When I got a car, there was McDonalds again: available, easy, quick. So is Subway, but it's not open at 3 AM. Which brings up ...
Lifestyle. Yes, the computer geek lifestyle ... what is it? We sit at desks for most of the work day. That by itself isn't healthy. Some of us keep strange hours, I know I do. Sleep? All over the place. Some of us eat poorly, though I don't think that's isolated at all to the computer industry.
There are some that have taken the same intelligence, logic (and sense of humour) they use in their computer work and apply it to their diets. In fact I've met some computer people that are downright health nuts (ever heard of bottled water with chlorophyll? it's green!). It depends on how interested we are in our own health.
Given the nature of the industry we are in we should be. At the very least, we're sitting a desk 4, 6, 8, 10, 12 hours a day -- we may want to counteract the negative aspects of that inactivity.
Which doesn't even consider what kind of implications our overall style of work has on our diet. It's very easy to say you'll change your diet but it's not so easy to do. It's not that much fun either. Subway over McDonalds is an easy choice and for lunch it's the least I can do. But there's a lot more.
What do some of you guys do?
# jid3rL Will Continue
I'm not going to stop development on jid3rL even though I'm working again. I'm determined to get it done, albeit slower than if I were still working on it ten hours a day.
The project has been punctuated by a few major refactors as I learn the domain of the problem. This is often the case in software development, and why the iterative/agile methodology centered on enabling refactoring is so powerful: I don't need to know the whole domain to start, because I'm not designing up front.
But don't forget: to refactor with confidence you need unit tests. Otherwise you will just accumulate regressions you have no idea about. jid3rL already has four hundred unit tests in less than a month. Crazy.
Speaking of unit tests, I've made an additional unit testing suite for jid3rL that tests against real mp3 files. It's up on the build page now. The original unit test suite just unit tests against the API with no mp3 files at all, and is what the jcoverage code coverage metric is based on. This is so the code coverage metric isn't skewed by tests involving files.
There are over forty frame types in the various versions of id3v2 and each has its own arrangement of bytes in the payload after the frame header. I have to parse these bytes to read the frame and also generate the bytes from a frame object to construct a tag for writing. In jid3rL that's done individually in each frame right now, which is a terrible administration headache already (I have less than a dozen frame types implemented) and could be a source of quality problems in parsing and building. Not to mention that unit testing each of these frame types is a major effort and should be centralized.
I was looking through the id3lib C++ source code recently and saw something interesting: all of the frame types are specified as constant structs, and the parser and builder are generalized to take these constants and parse and construct frames based on them. I like this approach and I think I'm going to mimic it, but in jid3rL the frame types will still be classes and not constants.
There are some complicated frame types in id3v2 to watch out for. Some use one field value to determine the lengths of another field. Some have fields a few bits in length appended together which do not fall on byte boundaries. Rather than complicate the generalized parser and builder with these advanced frames I think I will parse/build them separately. We'll see which direction the refactoring takes.
AudioMan itself is temporarily on hold until jid3rL is in a usable state and is relatively stable. I'm not going to take a chance on it because with a serious enough defect jid3rL could seriously damage people's mp3 files. That would be bad. jid3rL is going to be the most highly tested piece of software I've ever written.
# Scoble's Link Blog
I've looked at Scoble's link blog a few times and it's just a big list of partially quoted posts. I've never subscribed to it. While it might be more convenient for him to dump a bunch of links quickly like that, I think it's having an important negative effect on the quality of his original blog.
Before I get into any criticisms of my own I think it's important to say that I value a person's right to choose about the content of their blog. If someone doesn't like your blog, they shouldn't read it -- it's that simple.
But Scoble's blog is on another level: it's almost a community on its own. I'm not saying he has more responsibility to his readers than any other blogger, but when he preaches about community building and doing that by linking to others, he should be careful to walk the walk to protect his credibility.
That's where the link blog fails. People enjoy being linked from Scoble's main blog because they get lots of traffic and discussion. I doubt they feel the same way about being linked from the link blog. It just doesn't have the same impact, the same community building force.
When you look at the pace he was going at though, he doesn't have much of a choice. Who on earth tries to personally aggregate 2000 blogs? People just don't scale like that, and it was bound to catch up to him and his blog. Is there an easy solution? No. Is Scoble's link blog a good solution? It might be for others, but it's not for me. I won't be subscribing, and that's unfortunate because Scoble often finds interesting stuff reading feeds and through his contacts.
I have a link blog too: my bullet blog on the right. I put only links there, and only stuff I really want people to notice. It goes in spurts, but I might add five links a week. Notice they are undated. Mark Pilgrim also has a link blog on the same page as his main blog.
I like this approach better because it's a solid connection between the main blog and the link blog. There's a credibility aspect to that, and the links are still in a high profile enough spot to be noticed by your blog traffic. Unfortunately this solution would probably not scale to Scoble's needs.
Update Sat 12:29 Scoble linked to this post on that link blog and I got less than a dozen referrals from it ... maybe my post title wasn't catchy enough? Maybe his link blog doesn't have many readers? Maybe many of his link blog readers are gone for the weekend? Many he just posted too many links in one day? Could be one of many reasons ... but any way you slice it, it seems that a link on the link blog doesn't have nearly the same community-building impact as a link on the Scobleizer.
Why am I picking on Robert? I'm not. :) I just find his blog and blogging to be an interesting case study. What he blogs is viewed by many times more people than most blogs, so he has special issues he has to deal with. Who knows, we may all be able to blog with the volume he does in the future but easier because we have better tools and procedures. We'll get better at blogging by watching extreme bloggers like Robert Scoble while thinking about and trying out ways to make reaching and communicating with communities easier. Blogging is still young -- there's lots of room for improvement on both ends of the channel; blogging and feed aggregation.
# Updated to MovableType 3
OK, I've transferred everything over to the new MovableType. If you have any problems with this blog now please let me know ... it should be all good now.
It's funny, even with no front web site the old MovableType installation got spammed 500 times since I moved it over. The spammers didn't even go through the web site to spam me, they just spammed the MT API. Tricky. This one hasn't been spammed yet, though I think I'll owe that to changing the file names to the post titles.
Update: it doesn't need the web site to spam comments because the CGI file that handles comments is in the MT install directory, not where the blog is. Once I remove the blog from the MT install, the entries won't exist and comments can't be added to them.
Update 2: I'm noticing a lot of referrals in my hit counter from unknown, which sometimes is an RSS feed reader opening up the post. Are my RSS feeds OK? The Movable Type default is to publish excerpts and I changed it -- my intent is to publish the full post.
Also, found some great summary and some tips about avoiding comment spam.
# Just Pile it On
Just got a copy of Visual Studio 2005 Beta 1 in the mail today, which I'm looking forward to checking out to see how much has changed since I compared the older Visual Studio .NET and Eclipse. Notice that VS 2005 doesn't explicitly mention .NET.
So naturally I'm looking for a "real" project to do in it. If a great idea for a project doesn't fall out of the sky, I'll probably write Tetris (TM) in .NET again. The first time I wrote it a hard drive crash erased the source code. :S Though I do still have the compiled version somewhere.
Update: I'll check out the .NET options for reading id3 tags, and if they are lacking I might port jid3rL to the .NET framework.
# Back to Work
After a little down time I've taken a full time job at Agnovi, a software company here in Ottawa. I won't be talking about the details of my job here, but I'm looking forward to it.
I'm also looking forward to moving back to downtown Ottawa. It's a renter's market now apparently too, which is sweet. If you know of any nice buildings downtown that I should check out, let me know. I'm looking for a one bedroom place.
# Comment Spam Blocking
I've been getting a lot of spam on this blog lately, so I've finally updated to Movable Type 3.0 and the MT-Blacklist plugin.
I also changed the URLs of the posts to correspond to the post title so they aren't as predictable.
I'll be transferring over my templates soon. Let me know if I forget anything. :)
Update Sunday 10:58pm Fun times! Not really. There are plenty of little glitches to fix. I moved my Bullet Blog over to MT3 too, because it was being spammed as well. It will probably take a week to tweak this site back to goodness again.
# Gmail Notifier
Google has released an official gmail notifier, which is good. There were a few unofficial ones bouncing around, but I couldn't get any of them to work well so I gave up. I'd rather have one that won't break anyway, straight from the source.
Instead of folders, gmail has labels. You can configure filters to automatically label a new email when it arrives based on subject, sender, etc. I use this for mailing list emails. Unfortunately gmail notifier does not see new labelled emails, only new unlabelled ones that appear in your "inbox". Why is that?
# Frame Fields Chart
To help me get an idea of the frames I have to parse in id3v2, I've made up a frame fields chart.
Remember that frames hierarchy I was talking about the other day? It's a recursive map of maps to store frames to ensures that I don't write over a frame with another with the same id.
About half of the frame types in the spec allow more than one frame with the same frame id. You have to parse the beginning of the frame to get to these fields to differentiate between them. I've marked those fields in the chart too.
The chart is almost complete, but there are a few question marks.
Update 1:05pm chart is done now.
# Leaning Towards Frames By Reference
If you managed to get though my babbling about big id3v2 frames (not just once, but twice) you might be asking yourself "why?" And that's a great question, because I could be trying to solve the wrong problem.
Let's say you keep track of large frames by reference; you keep track of the File, offset and length to find the frame. Then you read the tag on Monday, delete the original file on Tuesday and try to write the tag to another file on Friday. All of those frames-by-reference will be gone because the original file is gone.
But is this such a big deal? The tag still contains a lot of data: all of the frames that are small enough to be parsed to regular Java Objects. You could still write this data on Friday, and maybe tell the user of the library "hey, by the way: everything by reference wasn't written because the file is gone." That's the worst case.
More often though, you'll be writing to the same file the tag was read from. That file will still has all of the frames-by-reference, right? Yeah, probably. Maybe another program deleted one of them, but you can just skip that missing reference now because it's already gone.
Update 12:49: File, offset and length are not good references into a file to find a frame. Frame ordering can change over time, and the position of frames can be bumped slightly if previous frames increase in size. It's impractical to keep track of the offset changes that could happen in other applications.
A better reference is a pointer to the File, and the List of Objects I use as a frames hierarchy key. Then it won't matter if the position of the frame changes within the file, I can find the frame based on the jid3rL frame hierarchy I made up.
# jid3rL Frame Storage Types
So to summarize an earlier post about big frame sizes: id3v2 tags can be up to 256MB long, so technically speaking an id3v2 tag could have a single frame that's also 256MB long.
You may have read the comments on a post I wrote early in in jid3rL development. I explained that when a tag was bigger than the previous one, the whole file including the music had to be rewritten because of the way that file systems layed out files. To counter this, most tags are padded with zeros to allow a tag with more data to fit without needing to rewrite the whole file over again.
In the case of large frames, rewriting a file could turn into a hard drive space problem. If a tag is too long to fit over the old tag for a file F, I do the following.
So just before step 4, I have two copies of the file -- F with the old tag and T with the new tag. If the tags are 256MB then that's at least 512MB, nevermind the size of the music. :) If I use the first approach I explained yesterday where I copy frame data to a temp file so I always have it, then that's another 256MB I need.
Using this extreme example you can see the jid3rL library will need to set a limit on the maximum supported frame size, after which the library could just refuse to modify the file. It could also just ignore gigantic frames and not rewrite them the next time the tag is written. Hard to say ...
This makes three different types of frame storage:
Since jid3rL is an open source library, the threshold settings for these storage types could just be constants in a Java class. They won't need to change during program execution, but software developers using the library might want to tweak these thresholds themselves and recompile the JAR.
It would be nice if the first two frame storage types looked the same from the outside with a good abstraction. When I'm writing a tag to a file, I don't want to have to worry if a frame is coming an array of bytes or a file, I just want to take the list of frames and write them out one after the other.
Roy and I were also talking about frame ordering within the tag the other day and its implications. I'll have to blog about that next.
# id3v2 Frame Gigantism
Now that I'm done doing simple id3v2 tags with jid3v2, a small hurdle has come up. Images. Up until now, I've been storing the bytes of frames in byte arrays. When you get to images, which can be 100 kilobytes or more, then you start chewing up RAM with that kind of code.
Images aren't the only frames with this problem. Technically in id3v2 the frames have very high length limits, which are specified in the frame header and differ by id3 version. 2.2 uses three bytes, 2.3 uses four bytes and 2.4 uses four sync-safe bytes. An effective library should be able to handle these limits.
**Note that the tag length is also a syncsafe 4 byte number (28 bits), which is the total length of all of the frames. So a frame with a length over 2^28 bytes isn't possible.
Those are some big maximum frame sizes. The way I see it, I have two options for these large frames. The first is to make a copy of these long frames and store as files somewhere; maybe somewhere in the user's temp directory, maybe a directory that you specify to the library that's in your application's directory. Then if you need to write that frame to a tag, you copy that frame's file over to the new tag. An obvious disadvantage of this approach is that it takes time to copy bytes like this.
The second option is to make a reference to the frame: the file, the starting position (offset) and the length. Then you don't have to copy the data in the frame to a temp directory in between. The disadvantage is that you can't move the source file, or the frame reference points to a missing file and the frame can't be written -- d'oh.
This might make you question the lifecycle of a
# Automatic Object Test Generation
Something that's becoming apparent after working on jid3rL is that it's nice to have well tested objects but it's a pain to test them. If you start from the bottom with very well tested objects, then you'll have less defects when you integrate them together and use them with other code.
The problem is actually testing them, making them bulletproof. The equals() and hashCode() methods have very specific "contract" conditions that are easy to test. But writing the tests is tedious, especially for simple immutable objects like the ones I use in jid3rL.
It would be nice if I could generate these tests automatically and save a lot of work. It could be an Eclipse plugin ...
You mark the member variables that you want to count when determining object equality for that class, and then the tests are generated automatically. It could generate far more tests than you would normal do manyally, since it wouldn't take long to regenerate them again if you added a new member variable.
Speaking of that, if you did add a new member variable the Eclipse plugin could pop up an error/warning in the Problems view telling you to regenerate the tests again.
# jid3rL Wrapped in SimpleTag2
Ironically enough, after blogging about how the full jid3rL library might be "a bit much for general use", I ran into that problem integrating jid3rL into AudioMan. It was just a lot more than I needed.
I thought about the easiest way it could be presented if the user only needed to read/modify/create a few basic frame types from/in an MP3 file. I made a class called
Then if you want to read a file's tag, it looks like this:
# Two Tiered Approach for a Library
Implementing the id3v2 spec has been a lot more fun than I thought it would be. Yes, truly geeky fun ... but fun nonetheless. I'd really like to implement the whole spec, and I don't think it would be that difficult.
The only problem with implementing the whole id3v2 spec is that it's about three times more than the average programmer really needs/wants to read and write id3v2 tags. Most of the id3v2 libraries I've seen are far from complete, most only reading and writing the basic tags: Text Information, Comments and maybe Attached Picture and a few others.
So the API for a library that implements the complete spec might be a bit much for general use. It might be more useful to have a simplified API built on top of the main library that exposes much less functionality. Even better would be a library which exposes a simpler API but allows the library user to go into the more complex API if they need to.
This idea for this came from JFace, a Java GUI toolkit library built on top of SWT. JFace uses SWT to create an easier to use API for common SWT widgets, like wrapping an SWT Table with a JFace TableViewer. This saves JFace/SWT library users a lot of setup work and exposes easier to manipulate object models. Also, while using JFace, you can still get access to the underlying SWT objects to read and manipulate them.
Completely hiding complex details in a simplified API sounds like it would be much easier for me than allowing the user to dig deeper, though it may not be. If course the programmer would also have the option of just using the more complex API as well. I haven't given much thought on the specific implementation details, I'm just trying to empathize with the users of this library and what they'd want/need from it. Thoughts?
# When is a Feature "Complete"?
In relation to a standards specification like id3v2, you might say that a specific feature is complete when it implements its part of the spec.
Depending on the granularity/hierarchy of the feature, the completeness of it could also come into question. For example, the id3v2 tag header has an unsynchronization flag, meaning that the tag following the tag header is encoded to be unsynchronized. If I read the tag header and understand it - including the unsynchronization flag - am I then complete? Or is the "tag header" feature complete only when I support the unsynchronization feature itself? These dependencies are up to the project manager, but should be noted somewhere.
For that situation I've separated reading the tag header from support for the features dictated by the header. I might want to make this more explicit in the checklist.
Another issue you have to be concerned about is regressions, or functionality that once worked but has been broken. Once you indicate that a feature is "complete" in a document you'll probably want to maintain that completeness. It is not uncommon for dependencies between features to reveal themselves during development, and a dependency could break one complete feature while you're implementing another.
To ensure that this does not go unnoticed, a suite of regression tests should be used to confirm that completed features are still functioning as new features are added. It would also be easier to verify the effectiveness of this regression test suite if the tests were organized to correspond to the specification document. Then it's much easier for people to independently verify that the library functions properly.
Note that this level of "completeness" is often unique to libraries which implement specifications or projects following a more spec-driven waterfall software development style. Most software is dynamic, changing over time with changing user demands. If features are changing, dependencies between the features change and the definition of "complete" changes for those features.
This is when more concrete definitions of features for a specific version of the software are necessary, to leave little room open to interpretation and allow confirmation of completeness. The project manager will also need to have some way of keeping track of all of the testing results and project implementation status data; either a speadsheet or custom software.
The project manager then needs to ensure that the regression tests are up to date with the current state of "complete" for each feature as the project changes, otherwise the existing regression tests may given him a false sense of security that everything is OK. Code coverage analysis may be able to help identify weakly tested areas.
# Tracking the Progress of an Implementation of a Standard
Software applications are often moving targets. Customer priorities change over time, so the feature demands change over time. Maybe you're making a COTS software product and you want to bring it into a new market. There are lots of reasons.
Software libraries implementing standards, however, are not moving targets. They either implement the standard at a given version number or they do not . The standard for a specific version number will never change.
2.2 in Mar 1998
So to track the progress of jid3rL I've laid out a matrix of the features in each version of id3v2 and whether or not jid3rL implements them.
 If the standard is specific enough. If the standard leaves room for interpretation then whether or not a given software product actually implements the standard can be called into question.
# jid3rL is Building Nightly
I'm not quite ready to release jid3rL 0.1 yet, but it's building nightly from CVS now. If you haven't been reading this blog lately, jid3rL is the LGPL Java id3v2 library I've been working on so AudioMan can have id3v2 read and write support for MP3 files.
There are a lot of unsupported/unparsed frame types but the major ones are there. One of my main goals with this project is making an easy to use API, so I'm looking forward to feedback there. Good JavaDocs are high on my priority list, as is some sort of manual -- even in the form of API-use examples that people can just cut and paste.
I'm pretty happy with my progress in just 10 days. It will be strange switching gears back to AudioMan again.
# ClassCastExceptions are Useless?
Just curious, but how many people out there using static languages actually try to pass invalid types to methods? How many Java users commonly get ClassCastExceptions at runtime?
When using Java Collections I get ClassCastExceptions at runtime periodically, but it's usually because I'm wrapping the collection inside another class that has methods returning a specific type instead of just Object. When I retrieve the object from the collection I cast it to that type.
What's the real reason I get ClassCastExceptions? Because I refactor a lot. After a refactoring the wrapper class to use B instead of A you might be putting a type B into the collection and incorrectly casting it to the old A in a wrapper class method.
This problem will go away when generics are introduced and I can specify the type I want a collection to contain. The compiler will tell me I'm trying to put a B into collection<A> and I'll be forced to refactor it to collection<B>. Then the compiler will tell me I'm casting an item from collection<B> to an A (the place where the ClassCastException formerly occured for me). Actually Eclipse will tell me these things as I type, instead of at compile time.
As I understand it, and I could be way wrong as usual, languages like Smalltalk don't have this problem because they don't enforce type, so you never cast an object coming out of a collection (I assume there is a complementary group of classes to Java Collections in Smalltalk). If I were refactoring the same problem in Smalltalk, without the ClassCastException I may never know that I'm returning an object of the wrong type after the refactor (A instead of B). To catch this problem, you'll need good unit tests and you'll need to change them all from using A to B. You could always just nuke the A class, and the compiler will tell you it's invalid. With static typing in Java and generics, you don't have to nuke A.
 Note: this is different from putting in new type A' and incorrectly casting it to the old version of A. A refactoring IDE like Eclipse would change all of the references of A to A' and you wouldn't get ClassCastExceptions because A doesn't exist any more.
 Eclipse is compiling the code in the background as I type so I don't need to compile it after.
Update 11:41 am James responds:
You don't have this problem in Smalltalk because it's not the sort of problem you tend to get yourself into, period. Say I had a collection holding Foos. If I refactor, and I end up having a collection holding Bars (completely incompatible), then I have bigger problems. Even so, I would have had to refactor all the surrounding code that accesses the collection elements - and if I didn't have tests under those circumstances, I'm in trouble whether I have static typing or dynamic typing. To be brutal, if you trust the compiler to solve this for you, then you shouldn't be writing code.
I agree, testing is critical. If you're making up a new class you have to unit test it, and tests will catch refactoring errors from B to A in Java or Smalltalk.
I wouldn't trust the compiler to test the result of a refactoring for me any farther than I could throw it, but with static types and generics it will be a lot more obvious when I break something.
This probably seems like type hand-holding, but it's nice to see something break immediately as you type it, just like you expect it to -- even before you run the unit test suite. It's good immediate feedback.
If you were unable to unit test (as stupid as that sounds, most people still don't) would you rather use a dynamically or statically typed language? Would static typing help? Assume for this question you are equally familiar with Java and Smalltalk and neither has an advantage in that respect.
Personally I'm still in Java land because it's what I need to know -- at the very least -- to get a job. Java is in more places than Smalltalk or Python. I'll move on to Python or Smalltalk in a bit, and if I've used Java and know it well I'll be better informed about the differences between the languages. I still have faith that I will "see the light" in regards to dynamically typed languages. I was just stating a case for ClassCastException in Java.
Update 1:34 pm Ian Bicking says on his blog:
A tool can analyze statically typed code and say with some confidence exactly where a class or method is used -- in Smalltalk, Python, or other dynamically typed languages, refactoring is just a string match. Good naming practices can make that string search more reliable, but it's still just strings, not a fully type-annotated source.
# jid3rL with FramesGroup
Here's an update to my earlier post, An Intro to jid3rL. The classes and constructors have changed to:
Keep in mind that the green (public) classes are the only ones accessible to users of the library. This is a update of the older explanation of these classes, which you might want to read first.
I made the two "constant" classes I talked about last time. These types of classes are used because Java doesn't have enumerations -- yet. They limit the range of input on a parameter and simplify testing. Those two classes are Version and Language.
There are only three Version instances: 2.2, 2.3 and 2.4. The byte used in the private constructor are just the minor version numbers 2, 3 and 4. The Language class has contants for all 500 or so languages in ISO-639-2. Yes, it took quite a bit of tedious/brainless typing to make this class! To improve readability I didn't capitalize the Language constants, so English is Language.English. Language is only used in CommentsFrame so far.
Before I fixed my flag reading code I thought I was going to have to read id3v2 tag extended headers. Turns out the test files I have don't have extended headers but I'm going to save this work anyway.
To simplify the Tag2 class I took all of the frame-related methods out of it and made a new class: FramesGroup. FramesGroup uses the Hierarchy class I blogged about yesterday to organize frames. Tag2 only has one FramesGroup.
# The id3v2 Frames Hierarchy
There are many id3v2 frame types that allow instances of the same type with the same id to exist in a tag. One of the most frequently used is the Comments frame type.
The Comments frame type has the following pieces of information:
The id3v2 spec says that you can have multiple Comments frames as long as they don't have the same language and short description. So you might have a few comments with a different short description, or with the same short description but translated into several languages.
As an aside, WinAmp and iTunes both use a zero length string for the short description of user-entered comments (iTunes likely just followed WinAmp's de facto standard). iTunes also has a few custom comments it puts into files you rip from CD with it. One appears to be a string of combined CDDB lookup numbers, one of which should actually be stored in the Unique File Identifier frame. The other comment appears to be normalization data.
Other id3v2 frame types only let you have one frame of that type with the same id. This is true for the Text Information frame type, which holds values like artist name, album name, track number, etc. You can only have one Text Information frame in a tag with a given frame id.
A good data structure for holding frames is a Java Map. Since each different frame type has frames with a unique id, you map the id to the frame. If the tag doesn't have a frame with that id, the map returns
Oh yeah, those Comments frames -- they all have the same frame id. If I only used the frame id with the
With my Comments frame example, the path will be:
1. frame id
Text Information frames can be found in the
# Java Signed Bytes and Decoding Flags
The id3v2 spec has bit flags in it, which are either 1/0 (true/false). These flags are jammed into bytes and each byte can contain up to eight flags.
For example, here's an excerpt from the 2.3.0 spec for the tag header flags:
Unsigned byte values range from 0 to 255. The most significant bit is on the left (a, worth 128 if on) and the least significant bit is on the right. If you don't know how to decode bits from an unsigned byte, you can read up on it.
The problem is that Java represents bytes as signed bytes, which range from -128 to 127. I'm reading signed bytes from the files, so I have to deal with them. You might say "OK, just take the signed byte and add 128 to it to get an unsigned byte". At first glance that might seem correct, but it's not. To understand why you have to get under the hood of the Java signed byte.
Java stores its signed bytes in two's complement notation. If only the
The trick is to use a bitwise operator in Java that doesn't care about sign: the unsigned right shift operator
How did I verify my code works? Unit testing, of course. Here are the tests I do:
I could be much more comprehensive, especially on the negative side. But this seems good enough for now.
# An Intro to jid3rL
OK I'm ready to talk about my id3v2 library now, which I'm going to name jid3rL. I don't want to jinx it, but everything is working out well so far. The trick, as I guessed in my last post, was to approach it from the write point of view instead of the read. Then things became clearer and cleaner.
If you're unfamiliar with the id3v2 spec, you can read about all of the versions here. It looks intimidating at first but you only need to know about 25% of it. The rest isn't used very much and I'm not sure about supporting all of it in this library.
Here's a simple rundown of the spec. An id3v2 tag is a bunch of bytes at the start of a music file, usually MP3 files. It consists of, in order:
- tag header
Frames contain data about the music in the file: metadata. Things like the song name, artist, album name, etc. Each frame is:
- frame header
The id3v2 format has gone through 3 iterations of spec: 2, 3 and 4. These are commonly referred to as id3 versions 2.2, 2.3 and 2.4. This is why there is no 2.0 and 2.1.
The only things I've discovered that are different about the three specs are:
- frame headers
This has interesting implications for a library, since I can easily abstract these details out and use the same objects for:
... and that's what I did. Here are the objects I have in the library, all in the same package. Constructor signatures listed.
All of the classes generate immutable objects. This means they have final member variables initialized in the constructor which cannot be changed and that makes the objects automatically thread-safe with no additional work. Most classes only have only one constructor. Sun Java guru Joshua Bloch explains immutable objects and why you should favour them in his book Effective Java.
Why is there no
Notice the two constructors for
There are some other candidates for constant classes, like how I did
One of my primary goals is to have a good API. It shouldn't break in later versions -- I can add to it (for example, new supported frame types), just not change method signatures or remove from it. I want to be in that state before I release 1.0.
OK peanut gallery, what do you think of my first run? There are enough software engineers reading this blog that I should have some good peer review. I'll post the full code soon, under the LGPL.
# Empathize with your Blog Audience ... if you want
There's no specific spirit to blogging, and that's probably the point. It's the freedom to be able to publish whatever you want to say on the Internet for anyone in the world to read. Now that's freedom.
Then some bloggers gain an audience, either because the content of the blog is interesting or their friends and relatives are curious and want to read it. Sometimes knowing you have an audience affects the content of the blog and sometimes it doesn't. Some people can blog like no one is watching, as it were.
A really interesting use of blogging is by celebrities, often misquoted or misrepresented by the press, who want to set the record straight in their own voice much to the horror of their PR reps. With a blog they can get straight to their audience. Examples are Mark Cuban and Billy Corgan.
If your intent is to get down your own thoughts despite your audience, that's cool. Your blog then becomes a searchable and highly accessible notepad. But if you're trying to communicate an idea to your audience, you have to empathize with them. You need to explain the back story, expose acronyms, technical details and confusing parts. Try to make your posts explain themselves, even if it's just linking to old posts or other places where people can read up on specific background information.
This kind of thing takes a lot of work -- I'm not that great at it either but I'm working on it. It takes more work to prepare a post this way, sure. But doing it will make your blog better for your audience.
# Library Licenses
Back into uncharted IANAL territory again with software licenses. Before I start posting code on this blog I wanted to get licensing out of the way for the Java library I'm writing.
Do any of you have opinions on a good open source license to use? Here is my intent:
Anything else I should be worried about?
So far I'm considering:
The GNU General Public License (GPL) is too viral to be used in this case since any program that uses GPL code must also be GPL. That violates term #2 above.
Writing this id3v2 library has turned out to be just as hard as I thought it would be. I'm still in the experimentation stage at the moment, trying to figure out what the code can do for me. There's no unit testing outside of using real mp3 files to verify everything reads properly. You could say that I'm prototyping while learning the domain.
One big problem is starting the library as a read library. This skews everything over to one side, when I want to be able to write as well. A useful exercise may be to start it as a write library instead, and see how much is the same.
The other issue is encapsulation; hiding details not only to make the API simpler, but also to provide fewer entry points for library users. This lets me change more of the guts of the API without breaking the contract. Library users like that.
Java packages are useful for encapsulation. Java classes can be either public or package visible. Package visible classes can't be seen outside of the package they are in (or the library for that matter), so they're good for encapsulating functionality. I only want to expose certain classes to the user of the library by making them public. The others will be hidden from the user.
When I get a good enough first run at it done I'll post some details.
# Early Thoughts on Library/API Writing
While writing this little id3v2 library, the first I've written that I plan to release and support, I've been thinking about a few of the issues people have when writing libraries and APIs. Here's a few I like:
There are some other issues for dealing with data "in the wild". You can't just throw an exception when you get bad data, you should be expecting it. So you have to have a way to represent it that tells the API user "hey, this is bad".
It reminds me of the discussions around malformed syndication feeds and what feed readers should do with them. When developers started thinking more about the users and less about themselves, they realised that reading malformed feeds was a good idea. But the API should still indicate it's malformed.
The good news about id3v2? The last version is four years old. In high tech terms that's ancient, the specification entropy has slowed and it doesn't look like it's going anywhere. This reduces risk and the need for agility.
Despite its age, id3v2 is actually gaining in popularity. It's the de facto tagging format for mp3, so companies will trust it. It has hardware support. It's used in popular music players and online music stores. It's everywhere. It's amazing that there isn't a free Java implementation out there that's still supported.
# Tiger Woods I Am Not
I golfed at the Renfrew Golf Club on Saturday with some friends. The course hasn't changed much in the years since I was in high school and golfed it regularly.
We got out there at about 8am, and it was raining hard. I pressured my friends to just get out on the course, hoping that eventually it would die down. After the first hole (the 10th, we golfed the back nine) two of the guys dropped out and went back to get a raincheque. But my other friend and I pressed on.
It didn't stop raining until about the 15th hole. On the 14th, a 175-yard downhill par 3 with a forest down the right hand side, I picked my 5 iron. I planned on hitting it with a 75% swing. The rain was still coming down. My grips were wet and my glove was soaked.
I didn't think too much of it until the club slipped out of my hand on the follow through of my tee shot and -- because I'm left handed -- went flying towards the woods on the right hand side, end over end. The tee is elevated so naturally the club had no problem hitting the top of the trees first.
I went down to the landing spot to search for my club. I was looking around on the ground when my friend told me my club was actually 30 feet in the air with the end of the club stuck in a Y branch. Luckily the tree was only about 6 inches wide at the base and I could shake it. Unluckily I didn't have the inertia to jar it loose.
So my friend came down and gave the tree a good shake and the club fell down. The worst part? I also lost the ball, even though I thought it went straight off the clubface and dribbled down the hill off the tee. Both of us were too busy watching the club fly into the woods with our mouths open. Ha.
I ended up scoring a 52 in the rain. One par. Lots of fun. Not too shabby.
# id3v2 throws UnsupportedOperationException
After some searching for a Java id3v2 library to use with AudioMan, I'm about ready to give up. Surprisingly enough, even though the last version of the id3v2 spec has been out for almost four years no one has made a complete free Java implementation of it.
If that wasn't enough, the current incomplete implementations aren't being maintained. So if I have a problem (if my users have a problem), I have to fix it myself. Welcome to the world of free software. :)
So it's looking more and more like I'll have no choice but to make yet another incomplete implementation. I just can't bring myself to accept the risk of using any of these other ones. None of the ones I've seen even have a single unit test. Yikes.
The good news? A few of the implementations have good ideas. So I can take those good ideas and make them better. I can also test the crap out of my library .... why? Because I'll insist on it. Besides, making things bulletproof is fun -- and Roy loves shooting bullets at my software.
The bad news? This will take me at least a week. Time to put my head down and hack ... I mean uh ... TDD. :)
PS> I updated the resume. I'll be looking for a job soon.