This document provides some information about how the JSourceObjectizer can be used:

  1. What's needed to use the JSourceObjectizer
  2. How to parse a Java® source and get its JSOM tree
  3. Some information about the JSOM types
  4. Two possibilities of walking through a JSOM tree
  5. Comparing Java® source fragments (or even complete source files)
  6. Some tips and tricks

1. What's needed to use the JSourceObjectizer

The JSourceObjectizer distribution contains a directory called lib with three jar files:

  • the JSourceObjectizer itself
  • an ANTRL runtime distribution (which version depends on the JSourceObjectizer version)
  • a HSD core library

To use a certain version of the JSourceObjectizer ensure that the ANTLR runtime library and HSD core library bundled with the appropriate JSourceObjectizer version are used. For instance, another ANTLR runtime may also work but this can't be guaranteed, of course.

Required Java® version

The JSourceObjectizer V1.59 needs Java® 8 or later.

Back to the top


2. How to parse a Java® source and get its JSOM tree

One of the shortest way to parse a Java® source code with the JSourceObjectizer could look like this:

    import com.habelitz.jsobjectizer.unmarshaller.JSourceUnmarshaller;
    
import com.habelitz.jsobjectizer.jsom.api.JavaSource;

    // args[0] must state the path and file name of a Java source file.
    public static void main(String[] args) {
        
JavaSource javaSource = null;
        try {
            javaSource = new JSourceUnmarshaller().unmarshal(new File(args[0]), null);
        
} catch (JSourceUnmarshallerException e) {
            
e.printStackTrace();
        
}
        if (javaSource != null) {
            // do something with it
        }
    }

The method JSourceUnmarshaller.unmarshal() returns an object of type JavaSource, which is the JSOM type that represents the root of a parsed Java® source file. The next section deals with some fundamentals about JSOM types.

Back to the top


3. Some information about the JSOM types

The base type JSOM

The type JSOM (Java® Source Object Model) is the supertype of all types that represent a more or less complex part of a Java® source. For instance, the type JavaSource represents a complete Java® source file with all its type declarations and so on. On the other hand the type ModifierList represents just a list of modifiers that may be stated with method declarations or local variable declarations. But both, JavaSource and ModifierList are subtypes of the type JSOM.

Normally a super-type shouldn't know of what concrete subtype it is. However, this is not very practical for the JSourceObjectizer. Imagine getter methods that return the content of a local scope (a method scope or the scope of a catch block, for instance). There could be getter methods for any kind of source code fragment a local scope may contain (various statement types and local variable and/or type declarations). This may be okay in certain circumstances but the order of the local scope's content will get lost and this is something that isn't okay in most cases. So the JSourceObjectizer takes a middle course for such cases. Continuing with the local scope example there're getter methods that return just all local variables or type declarations, all statements or even all the content with a single getter call. Of course, the last getter must return a container holding super-types of the concrete local scope's content elements.

Therefore a JSOM object must now of what concrete super-type it is. Therefore, the interface JSOM defines enum constants for a lot of JSOM subtypes and the method isJSOMType(JSOM.JSOMtype pType) that can be used to find out if a JSOM object is of a certain JSOM subtype. But don't panic: there're a lot of JSOM getter methods that already return an object of a concrete JSOM type. For instance, the JSOM type ClassTopLevelScope does not just declare a (fictive) method like getContent() which returns a load of plain JSOM objects and that lets it up to the caller to find out of what JSOM types the returned objects are. Instead, ClassTopLevelScope defines methods like getMethodDefinitions() or getConstructorDefinitions() that return lists of objects which are already of type MethodDefinition and ConstructorDefinition respectively.

However, there's still one important point regarding the constants defined by the enum JSOM.JSOMType that should be explained here. JSOM.JSOMType defines only one constant for all kinds of expressions and one for all kinds of statement block elements*: JSOMType.EXPRESSION and JSOMType.STATEMENT_BLOCK_ELEMENT. Therefore, the call any JSOMObject.isJSOMType(JSOM.JSOMtype.EXPRESSION) only tells if a JSOM object represents an expression but not of what kind of expression it is (if it is an expression at all). The types Expression (the base type of all JSOM types representing an expression) and StatementBlockElement (the base type of all JSOM types representing a statement block element) define their own enum constants for their various subtypes. But, nevertheless, the JSOM interface also defines the methods isExpressionType(Expression.ExpressionType) and isStatementBlockElementType(StatementBlockElement.ElementType). If it should be checked whether a JSOM object is of a certain expression type or statement block element type it's not necessary to call the method isJSOMType(...) first to find out if the JSOM object represents an expression or statement block element at all.

* A statement block element is something that can be stated within a statement block scope, i.e.within a method scope or a compound statement scope, for instance; therefore, a statement block element can be a statement, a local variable declaration or a local type declaration.

The JSOM interface types

Once a Java® source has been parsed a JSOM object of type JavaSource will be returned by the JSourceObjectizer's unmarshaller (see the code example above). This JavaSource object is the entry point for walking through the complete parsed Java® source. To get a feeling for the JSOM interface types it's recommended to skim through the JSourceObjectizer's API documentation which is part of the downloadable JSourceObjectizer distribution.

The following list describes the content of the packages that are most relevant to JSourceObjectizer users:

  • com.habelitz.jsobjectizer.jsom
    This package contains the interface JSOM, i.e. the base type of all JSOM types. This interface contains an inner enum declaration JSOMType which defines contants for all JSOM types excepting constants for all concrete expression and statement block element types, as already mentioned above.
  • com.habelitz.jsobjectizer.jsom.api
    All JSOM types representing a certain element that can exist within a Java® source and that have nothing to do with expressions or statement block elements can be found within this package.
  • com.habelitz.jsobjectizer.jsom.api.base
    Some JSOM types have a common basis and differ only in some aspects. For instance, both a class' top level scope and an interface's top level scope can have abstract method declarations, field declarations and so on. But an interface top level scope never has a constructor definition. Therefore the JSOM types ClassTopLevelScope and InterfaceTopLevelScope are not derived from the type JSOM directly. Instead, the common features of such JSOM types are bundled together by appropriate base types. Such base types can be found within this package.
  • com.habelitz.jsobjectizer.jsom.api.expression
    This package contains all the JSOM types that deal with expressions. The base type for all kinds of expressions is Expression. This base type also contains an inner enum declaration ExpressionType which defines constants for all concrete expressions.
  • com.habelitz.jsobjectizer.jsom.api.statement
    This package contains all the JSOM types regarding statement block elements, i.e. statement types and types for local variable declarations and local type declarations. The base type for all kinds of statement block elements is StatementBlockElement. This base type also contains an inner enum declaration ElementType which defines constants for all concrete statement block elements.

BTW: It's also possible to parse just certain Java® code fragments, i.e. the source passed to the unmarshaller mustn't be a complete Java® source file.

The implementation of the JSOM interfaces

All the JSOM interface types are implemented by the content that can be found within the package com.habelitz.jsobjectizer.unmarshaller.jsombridge and its subpackages. For every JSOM interface type there's also an appropriate implementing (maybe abstract) class within one of these packages, with the same name as the JSOM interface type but prefixed by AST2. For example, the interface com.habelitz.jsobjectizer.jsom.api.statement.ReturnStatement is implemented by the class com.habelitz.jsobjectizer.unmarshaller.jsombridge.ast2api.statement.AST2ReturnStatement.

Unless you need to change or improve these implementations it's recommended simply to ignore all the AST2Whatever stuff.

Back to the top


4. Two possibilities of walking through a JSOM tree

There are two possibilities of walking through a JSOM tree:

  • the manual way
  • using the JSOMTraverser call back methods

 

Walking through a JSOM tree manually

Nearly all JSOM type interfaces provide getter methods to get the content or elements of a certain JSOM type and it's possible to walk through a JSOM tree only by using these methods. But they should be used very carefully because it's likely that a lot of them may also return null. This is the case for all the content or elements that are optional for a certain JSOM type and that doesn't actually exist. For instance, most method declarations don't contain local type declaration, so calling the appropriate getter to receive local type declarations from a method will almost always return null.

On the other hand, there are also a lot of getter methods that never return null because they return obligatory content or elements. Therefore it's not a bad idea to have a look into the JSourceObjectizer's API documentation if you are unsure whether a getter method returns an optional or obligatory content and may therefore return null or not.

Walking through a JSOM tree by using the JSOMTraverser

About the JSOMTraverser

For most cases using a so-called JSOMTraverser is the prefered way for traversing a JSOM tree. This can reduce the problems regarding getter methods that may return just null for optional and non-existing content (see the paragraph above). Furthermore, traversing a JSOM tree manually can become a fiasco (this is the reason the JSourceObjectizer has been developed at all).

The base type of a JSOMTraverser is the interface TraverseAction, which can be found within the package com.habelitz.jsobjectizer.jsom.util. The interface TraverseAction declares the following pair of methods for most JSOM types:

  • performAction(AnyJSOMType pJSOMType)
  • actionPerformed(AnyJSOMType pJSOMType)


While traversing a Java® source via a JSOMTraverser, i.e. a TraverseAction object, the performAction(...) methods will be called immediately when a certain JSOM type object becomes the current traversing candidate and with this object passed as argument. The methods actionPerformed(...) will be called immediately after a certain JSOM type object and all its content and elements that are of type JSOM have been traversed.

What should happen within these methods is up to the individual needs, of course. Because it's very likely that not all of the performAction(...) and actionPerformed(...) methods are of interest for a certain task there's also an adapter class TraverseActionAdapter that implements all the TraverseAction methods but with empty method bodies.

How to start traversing with a JSOMTraverser

The interface JSOM declares the method traverseAll(TraverseAction pAction) and is therefore available for all JSOM subtypes. If this method gets called on any JSOM object the following will happen in the stated order:

  1. At first the method pAction.performAction(this) will be called.
  2. Then, if the current JSOM object has any JSOM type members, the method traverseAll() will be called on all of these members with the TraverseAction object passed to the initial traverseAll() call as argument.
  3. Finally the method pAction.actionPerformed(this) will be called.

Traversion with a JSOMTraverser can be started with any JSOM type object. If it gets started with an object of the JSOM type JavaSource all the content of a Java® source will be traversed, of course.

Back to the top


5. Comparing Java® source fragments (or even complete source files)

Overview

Most JSOM types (the most concrete ones or their super types or even both) declare a compareTo() method to compare JSOM objects of the same JSOM type. This comparison is not done just lexically. For instance, such a comparison also takes into account if the order of compared JSOM child-elements, that belong to certain compared JSOM root objects, is arbitrary or not.

As a rule of thumb, if the order of two lists of JSOM sub-elements is arbitrary (modifier list, method declarations, import statements and so on), the comparison checks if each element from one list has an equivalent within the other one and reports missing elements. On the other hand, if the order isn't arbitrary (statement lists, formal parameter lists, ...), the sub-elements at the same position will be compared.

There're three main types used to report found differences:

  • SemanticDelta (an interface)
    Objects of that type represent comparison differences and are returned by the compareTo() methods.
  • SemanticDeltaDescription (an interface)
    Objects of this type provide a textual description for a certain comparison difference; the message texts can be customized.
  • Delta (enum types that implement the SemanticDeltaDescription interface)
    For each possible (or at least supported) difference there's an appropriate enum constant.

Delta and SemanticDeltaDescription

A lot of JSOM interface types declare an inner Delta enum type defining constants for various kinds of differences that may be found when comparing two JSOM objects of the same type. If a certain JSOM type doesn't contain such an inner Delta enum the reason for that and most likely some hits and tips are described within the type declaration's main comment.

For the most time (if not always) a user of the compareTo() feature hasn't to deal with the Delta enum constants directly. However, there're two exceptions:

At first these enum declarations implement the interface SemanticDeltaDescription and the default description messages are just the identifiers of the enum constants. But, as said within the list above, the message texts are customizable - simply via calling the method setDescription(String) on a certain Delta enum constant.

Secondly a description might be a little bit cryptic (at least if not customized - but maybe even then :-)) . If the reason for a found difference isn't quite clear it may help to view the appropriate Delta enum constant declaration - they're well documented what hopefully helps in such situations.

SemanticDelta

A list of objects of this type is returned by the compareTo() methods if differences have been found (but null otherwise). A SemanticDelta object provides the following information:

  • The compared root JSOM objects that have been recognized as being different.
  • The descriptions of found differences.
  • The different JSOM sub-elements of the compared JSOM root objects.

The actual difference or differences of compared JSOM objects may be deeply nested. For example, if two complete and large Java® source files have been compared and if there are differences within deeply nested local scopes, the root JSOM objects returned from the root SemanticDelta methods getComparedJSOM() and getComparedToJSOM() are the compared JavaSource objects. Then the SemanticDelta methods returning the sub-deltas provide information regarding the method/constructor declarations or static/instance initializers that contain the differences. These sub-deltas then provide further sub-deltas themselves for the different method/constructor (or whatever) scopes and so on and so forth up to the nested scope that finally contains the statements and expressions that are different, i.e. this continues until the last bunches of sub-deltas have been reached that deal with the actual differences.

This means, the comparison actually builds SemanticDelta trees with maybe tight and/or long branches. Because it's a thankless task to walk such a SemanticDelta cascade manually the SemanticDelta type provides the method getLeaves() which, when called on returned root SemanticDelta objects, collects all the informations of the actual differences and filters all the noise from and between a root SemanticDelta up to the leaves representing the actual differences.

Back to the top


6. Some tips and tricks

Partially disabling and (re)enabling the traversion of JSOM type members

The JSOMTraverser interface TraverseAction defines the methods isMemberTraversingEnabled() and setMemberTraversingEnabledState(boolean). These methods are already implemented by the TraverseAction adapter class TraverseActionAdapter and the default value is true.

While traversing a JSOM tree with a JSOMTraverser the members of a JSOM object will only be traversed if isMemberTraversingEnabled() == true. This means that traversing such members can be disabled and re-enabled at any time. For instance, if the content of all statement block scopes are out of interest it would be a waste of time to traverse them. To disable traversing the content inside a statement block scope simply override the methods performAction(StatementBlockScope) and actionPerformed(StatementBlockScope) within your TraverseAction implementation as follows:

    public class MyJSOMTraverser extends TraverseActionAdapter {

        // ... my individual content ...

        @Override
        public void performAction(StatementBlockScope pStatementBockScope) {
            setMemberTraversingEnabledState(false);
        }

        @Override
        public void actionPerformed(StatementBlockScope pStatementBlockScope) {
            setMemberTraversingEnabledState(true);
        }

        // ... my individual content ...

    }

Disabling traversion partially not only saves time but may also save memory because a lot of JSOM objects are created on demand when calling a getter method on a JSOM object that returns JSOM children. As a matter of fact, after parsing a complete Java® source only the JSOM type JavaSource has been created.

Nested usage of different JSOMTraversers

The nested usage of different TraverseAction implementations can simplify the one or another lookahead job. If one of your performAction() methods has been invoked it could be the case that you need some information about the members of the currently traversed JSOM object. But no information about these members exists at this point of time. Just waiting until the matching actionPerformed() method gets called should be the best choice in the majority of cases but in some situations waiting until the members have been traversed could rise some problems.

To give an example for such a problem, imagine that you have to do some tasks within the TraverseAction method performAction(MethodCall pMethodCall). Furthermore imagine that in order to work on these tasks you need some information about the arguments passed to the method call pMethodCall. Traversing the arguments manually could possibly be the right choice. But an argument can be a very complex expression and traversing such expressions manually could be the hell. At the first view it seems to be simple just to let the traverser walk through the complete method call, collect the needed information about the method call's arguments and do the tasks for the method call within the method actionPerformed(MethodCall pMethodCall). This will work for method calls that have no method call passed as argument. But as soon as there's at least one argument in form of a method call all the collected information about the arguments must be stacked in some way.

To stack all the information about arguments will solve the problem but there's an alternative way. Within the method performAction(MethodCall pMethodCall) an instance of another TraverseAction implementation can be created that does nothing else then the lookahead job and that collects all the needed information just in time. Now call pMethodCall.traverseAll() with a temporary TraverseAction object that does the lookahead job as the argument. That's it.

Back to the top