Saturday, August 27, 2005

Deeper into the source...

I'm a child of the Mac UI revolution of the mid 80's. My first IDE was Lightspeed Paschal, and I still think the far superior way to "know" code is to watch it do it's thing directly in a source-level debugger. I still catch myself making hidden derisive snorts when I hear excellent and talented developers extolling the virtues of printf statements for debugging. Printf statements have their place, but when you have a debugger available, they are like trying to explore your backyard through laboriously drilled holes in a neighbors fence from two blocks away.

Especially to get to know new, complex code quickly, I like to trace it a few times in a debugger and familiarize myself with the runtime flow of the design, as well as the developer's language and style.

My wife and girls went shopping today and gardening and chores were done, leaving me with some precious weekend afternoon spare time. I put Miles Davis Get Up With It on the stereo and started tracing through NewTypeWizardPage.createType() on my PDE set-up of a recent integration build of Eclipse 3.2. (While eating some fresh cantaloupe melon and a cup of Horizon blueberry yogurt. I agree with Kent Beck that "there must always be food" but further than that, development is a complete experience. Just the right mood, tunes, and food make it go so much better....)

// Even more boring part starts here....
// You may skip to where the marginally less boring part starts up again, I won't be offended...

retrieves the user defined template by calling StubUtility.getCodeTemplate() with ID of CodeTemplateContextType.NEWTYPE_ID
Despite the similar but more limited text that has already been generated and put into "content" in NewTypeWizardPage.createType(), the template text is here, a seeming duplication with some additional content and some (like the package name and imports) missing content.
that immediately calls
CodeTemplateContext.evalulate(), passing the template read in above
TemplateTranslator.translate(), passing the text pattern (the user entered template) from the pattern
TemplateTranslator.findVariables() after "translate" strips out the dollars and braces.
this marks the positions and lengths of the identifying text of the template variables that are embedded in the translated string.
pop back up to CodeTemplateContext.evalulate(), now that all the variables have been parsed and translated, then it calls CodeTemplateContextType.resolve(), which calls TemplateVariableResolver.resolve() on each template variable to expand the text of each variable and process them into the translated string.
It builds up an ArrayList of ReplaceEdit objects, each object representing the replacement of the template variable identifying text with the actual text value of the template variable that has been processed earlier in this operation.
Then TemplateVariableResolver.resolve() creates a Document object and initializes it with the translated text pattern that was processed above (now in the "buffer" variable).
Then it creates a MultiTextEdit object, passes it the variable positions and list of ReplaceEdit objects processed above, and calls "apply" on it (which resolves to TextEdit.apply) to call a big stack of subroutines that eventually sort everything out.
MultiTextEdit(TextEdit).traverseDocumentUpdating(TextEditProcessor, IDocument)
ReplaceEdit(TextEdit).traverseDocumentUpdating(TextEditProcessor, IDocument)
Document(AbstractDocument).replace(int, int, String)
Document(AbstractDocument).replace(int, int, String, long)
GapTextStore.replace(int, int, String) - this finally replaces the text.

Now pop back up to NewClassWizardPage(NewTypeWizardPage).constructCUContent(ICompilationUnit, String, String) and this newly created string is put into a new local string called "content". Went back and reviewed the relation between the calling createType "content" variable I mentioned early in this stack. It looks like that is just used to help initially create the "type stub" text that will eventually be inserted into this new "content" variable. It is not used later in createType after constructCUContent returns.
Back in constructCUContent, it creates a new ASTParser to process the newly processed source text. If all goes well, it returns the content to createType, which spreads it around a bit and then wraps up. There is a case, however, when it creates a new string buffer, and reconstructs the content from the package and type information one more time and returns that. I haven't hit that case yet, but will probably need to know about it at some point.

// Even more boring part ends here, marginally less boring part starts up again....

I know seems like a lot of useless rigamarole for those not surfing right with me. That's perfectly right - it is a lot of useless rigamarole. But it may be helpful for reference later and save me a few round trips searching in the source.

The upshot is that the action in creating a source template is all in the template variable processing. Up in createType(), the constructTypeStub() routine creates the class declaration that I'm interested in, and initializes some variables for subroutines, then tosses everything down to constructCUContent() that grinds through the already created information to sort everything into its place. This suggests the way to get my changes to work are to create a sibling to constructTypeStub() inside createType() that constructs the stub for my new template variable, which is close to the same as the original, but is missing the brackets. This way, either the original template variable or the new template variable can be called, and the program will react accordingly. There are a couple of wrinkles to investigate, however. The first is what about user behaviors that violate the implied assumptions (using the original ${type_declaration} variable implies no brackets in the user part of the template, but the new ${type_declaration_no_brackets} implies there are. What if the user doesn't comply with the brackets? What if the user puts in both template variables? And, will this work at all in contexts where methods are initially inserted into the class source (like starting from an interface, or checking some of the "create method stub" options on the wizard.) Will have to get to those in a later installment...


Post a Comment

<< Home