Loosely coupled systems are good. Components with well defined dependencies make your large application flexible enough to be, oh, say trimmed down and reused for other purposes or packaged in flexible ways.
Actually, until fairly recently, we weren't being all that careful about enforcing dependencies between some components in the JDeveloper source code. Our build infrastructure was such that although there were some fairly rules about what was allowed to depend on what, in some cases they weren't actually written down anywhere and different developers didn't necessarily agree what the rules were.
Certainly, there was no actual enforcement of these somewhat nebulous dependency rules. If nothing else, years of developing software have taught me that, in a large disparate team, if you don't actively prevent developers from introducing despicably evil dependencies between pieces of code, those dependencies are guaranteed to creep in. That's doubly true if not everyone knows what the rules are in the first place. Enforcement helps spread knowledge about what's allowed. You create a bad dependency, and the world breaks until you fix it.
Our favourite bad dependency was between some of the components that make up JDeveloper's Java edition and its J2EE edition. The J2EE edition, being a superset of the Java edition, is allowed to depend on it. However, for much of 10.1.3, components in the Java edition have contained compile time dependencies on components in the J2EE edition. If you've ever been curious enough to look inside the humungous jdev/lib/jdev.jar in the Java edition, you'd probably be somewhat surprised to find a whole lot of J2EE and database related implementation code in there. It's there (and contributing to the footprint of the Java edition) exactly because of this bad circular dependency.
Raptor provided an amazingly effective push toward finally fixing this. We now have rigorous compile time enforcement of module dependencies, and are working on splitting up jars so that we can tailor the distribution specifically, knowing which parts require other parts. The net effect of this for users of Raptor and JDeveloper Java edition (in the next release, which falls under the all-encompasing moniker "Fusion", or plain old 11.0 to its friends) will be a decreased disk footprint as well as attendent performance improvements because a gazillion classes you don't need are no longer on the classpath.
Teasing apart the dependencies, and particularly the J2EE<->Java one, was not trivial and required some considerable creativity in some cases. One fairly random example of this was HtmlNode. HtmlNode is the basis of JDeveloper's support for HTML documents. It's one primary entry point to our HTML parser and contains a lot of useful stuff. For example, a common problem with HTML and XML is that information about the encoding you need to use to read those documents is actually in the document itself. Oh brother. That means that every time you read one of those things, you need to read it twice. The first time, you just dip in with UTF8 or whatever and figure out the encoding, the second time you actually read it with the encoding it wants to be read with. HTMLNode deals with all of this completely opaquely. You just acquire a Reader on the node and it deals with all the nasty encoding stuff for you.
This being a fairly useful thing, usages of HTMLNode had spread to a few places in the code base. For example, the help system needs to extract the title of the current help topic to set the tab at the top of the window. The trouble is, HTMLNode and the HTML parser are part of the J2EE edition and the Help system needs to work in the Java edition even when HTMLNode is not present.
My collegue (Armand "jojo" Dijamco) and I spent a bit of time looking into alternative ways of grabbing the title out of an HTML document to break this dependency. We considered a hacky renegade approach (just grepping for title basically), but this would have trouble if the HTML document were encoded in anything other than UTF8. We thought about moving the HTML stuff into the Java edition, but it would drag in so much other stuff. We considered using a third party lightweight HTML parser, but there were licensing issues. We looked at using the HTML parser the Ice Browser we use to render help content must have internally, but there seemed to be a general lack of clean separation in that API between HTML parsing and rendering (and eventually, we just plain couldn't figure out how to use that API do do what we needed).
Finally, we found what we were looking for in Swing (of all places). Of course, JEditorPane supports HTML rendering, so it stands to reason that Swing has HTML parsing support. And indeed it does:
// See if we need to decode the string
final String urlStr = url.toString();
final String str = URLDecoder.decode(urlStr);
if (!str.equals(urlStr))
{
nodeURL = URLFactory.newURL(str);
}
// SAX-style callback for the Swing HTML parser.
class Callback extends HTMLEditorKit.ParserCallback
{
private boolean inTitle;
private String title;
public void handleText(char[] data, int pos)
{
if (inTitle)
{
title = new String(data);
}
}
public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos)
{
if (t == HTML.Tag.TITLE)
{
inTitle = true;
}
}
public void handleEndTag(HTML.Tag t, int pos)
{
if (t == HTML.Tag.TITLE)
{
inTitle = false;
}
}
};
final ParserDelegator pd = new ParserDelegator();
final Callback callback = new Callback();
InputStreamReader reader = null;
try
{
// First attempt to read using ISO-8859-1. If the charset is
// later specified in a <META http-equiv="Content-Type" ...>
// tag, ParserDelegator will throw ChangedCharSetException,
// which is caught below and handled.
reader = new InputStreamReader(
URLFileSystem.openInputStream(nodeURL), "ISO-8859-1");
pd.parse(reader, callback, false);
}
catch (ChangedCharSetException e)
{
// This means the HTML file specifies a charset, so the entire
// file needs to be reparsed using that charset. The code
// below uses a regexp pattern to pick out the charset name.
//
// The syntax grammar for the Content-Type header is specified
// in section 5.1 of RFC 2045
// http://www.ietf.org/rfc/rfc2045.txt
final String spec = e.getCharSetSpec();
final Pattern p = Pattern.compile("charset=\"?(.+)"?\s*;?",
Pattern.CASE_INSENSITIVE);
final Matcher m = p.matcher(spec);
final String charset = m.find() ? m.group(1) : "ISO-8859-1"; //NOTRAN
S
InputStreamReader realReader = null;
try
{
realReader = new InputStreamReader(
URLFileSystem.openInputStream(nodeURL), charset);
callback.inTitle = false;
pd.parse(realReader, callback, true);
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
finally
{
forceClose(realReader);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
finally
{
forceClose(reader);
}
title = callback.title;