I have to admit I may have bitten off more than I can chew with this post, which accounts in part for the length of time it took me to publish it. As I started looking at javadocs and javadoc keywords in more detail, I realized the post had to include explanations of a lot of basic Java concepts. I hope I did an adequate job of explaining them, but I strongly encourage you to check out the official Java Tutorials to fill in the gaps (of which I'm sure there are a few). I recommend the entire Learning the Java Language trail, but the particularly relevant sections include the sections on Classes and Objects and Interfaces and Inheritance.
Access Modifiers and Special Considerations for Groovy
Access modifiers--private
, package-private (which is the default when no modifier is specified), protected
, and public
-- specify the context in which a class and its members can be read and/or modified in code. In the context of the basic usage of an API (when using it in a soapUI script, for example, as opposed to more advanced usage like making your own modifications to the source code), you're generally only going to deal with classes and members marked public
. The two most restrictive levels of access, private
and package-private are reserved for those classes and members that represent "internal wiring"-- strictly intended for use within a class (private
) or package (package-private), not intended for direct use by API consumers. In fact, the Javadoc tool excludes classes and members with these access modifiers by default, in which case they don't even appear in documentation. Members at the protected
level are only slightly less restricted in that they're inherited by sub-classes of their enclosing class (even sub-classes that aren't in the same package)-- for somebody using the API, this comes into play when creating your own sub-class, something beyond the basic scripting covered here.However, there's a very important caveat when working with Groovy. When trying some scripting in soapUI with the HSSFWorkbook class, I was surprised to see that I could in fact access the _sheets field. If you look at the documentation, this field is marked
protected
, meaning it should only be accessible from within the class, within other resources in the same package, or from within a sub-class-- a Groovy Script step doesn't fall into any of those categories. After investigating, I found this interesting post indicating that Groovy doesn't always respect access modifiers (see item 6)-- that's potentially a bad thing; there are good reasons why an API developer might restrict access to a certain member of a class, particularly when it comes to modifying its value. As the article says, just because Groovy allows you to do something doesn't mean you should do it; you may have to police yourself a bit-- be wary of accessing classes or members marked anything other than public
.Static and Final
Thefinal
modifier generally restricts a user's ability to modify a class or member, although it means slightly different things in different contexts. Applied to a class, it means the class can not be extended-- you can not create a sub-class based on that class. A final
method can not be overridden in a sub-class; in other words, you can't change the way the method is implemented, writing new code for it in your sub-class. The value of a field marked final
can not be changed. As with the more restrictive access modifiers discussed above, the final
modifier at the class and method level isn't something you'll need to worry about too much with basic usage of the API; it's more relevant when you start dealing with sub-classes. With fields, however, more often than not you'll see them marked final
in javadocs, and the distinction is important even in the context of basic scripting.The
static
modifier designates a class (as opposed to instance) member. A non-static member is associated with an individual object-- an instance of a class. I discussed the _sheets field of the HSSFWorkbook class above-- if you were to create two different HSSFWorkbook objects and add sheets to each, the _sheets field would be different for each object; each instance of HSSFWorkbook would have its own value for the field. A static
member, on the other hand, is associated with the class itself; when you access a class member, you access it through the name of the class, not the name of a variable representing an instance of the class:log.info(myWorkbook._sheets) //_sheets is accessed for an HSSFWorkbook instance named myWorkbook-- can be done in Groovy log.info(HSSFWorkbook.INITIAL_CAPACITY) //INITIAL_CAPACITY is accessed via the HSSFWorkbook class itself
It's common to use the
static
and final
modifiers together with fields to create constants-- so common, in fact, that there's a convention in Java to use all caps in these fields' names (as with the INITIAL_CAPACITY field above). Class constants often represent values that are frequently used; creating class constants for these values eliminates the need to repeatedly declare variables to hold them. Java's Math class has the static final
field PI, for example; similarly, the Color class has static
fields to represent frequently used colors: RED, BLUE, etc. Again, there's no need to create objects of class Math or Color to access these constants; they're called against the class names themselves (Math.PI or Color.RED, for example).Methods, too, can be marked
static
. Returning to Java's Math class as an example, aside from the standard methods it inherits from the object class, all of its methods are static
-- the class is essentially a toolbox of mathematical operations. As with static
fields, static
methods are accessed via the class instead of an instance.Method Return and Parameter Types
Method definitions in javadocs specify return types (the data types of results from functions) and parameter type(s) (the types of the values passed into the functions). For the most part, these should be pretty straightforward; for example, look at the WsdlTestCase class in the API documentation for soapUI. In the Method Summary table, find the getTestStepByName() method; in the left-hand (Modifier and Type) column you can see that the return type isWsdlTestStep
. In the Method and Description column, you can see in the parentheses next to the method name that the method takes a single String argument called stepName-- note that stepName is a somewhat arbitrary parameter name; any variable name could have been used when the method was created (of course, stepName is a good choice because it describes the significance of the String argument). From the perspective of somebody using the method, the parameter name itself is relatively unimportant; what is important is recognizing the method takes a single String argument and returns a WsdlTestStep object.The
void
keyword is used for the return type when a method doesn't return a value. The setName() function of the WsdlTestCase class, for example, changes a property of the test case without actually returning anything. For functions that don't take any arguments, the parentheses next to the method name are simply left empty.Some functions return multiple data items; for these, the "enclosing" data type is also indicated. For example, the getTestStepList() method of the WsdlTestCase class specifies a return type of
List<TestStep>
, meaning it returns an object of class List containing zero (the list could be empty) or more TestStep objects (more about what that means below). The getTestSteps() method returns an object of class Map; Maps consist of key-value pairs, so the return type specifies Map<String,TestStep>
-- each key-value pair in the returned Map consists of a String (the key) and a TestStep object (the value).In some cases, a method's parameter and/or return types are determined at runtime, in which case you'll see a slightly complicated-looking syntax. Look at the getTestStepsOfType() method, for example. The return type is given as
<T extends TestStep> List<T>
. There's no T class, of course. The T is used as a placeholder for a data type to be determined later. The <T extends TestStep>
part of the return type tells us that there's a restriction on T-- it has to be a type that's derived from the TestStep interface (a class that implements the TestStep interface, for example, or a class that extends another class that implements the interface, etc.), and the rest (List<T>
) tells us that the method returns a List of objects of that type. So how is the type actually determined? If you look at the method description column, getTestStepsOfType(Class<T> stepType)
tells us that type T is the type of the class passed in for the stepType argument-- so the argument determines the meaning of T in the return type definition as well.Return Types and Polymorphism
Above, when discussing the getTestStepList() method, I wrote that it returns a list of TestStep objects. If you were to look at the TestStep definition in the javadocs, however, you'll see that TestStep is actually not a class-- it's an interface. In my last post I mentioned that you can't actually create an instance of an interface. What's going on here? This is actually an illustration of the object-oriented concept of polymorphism-- it's a source of great flexibility and power in Java, but for me, at least, it was a source of some confusion when I first started working with javadocs. In a very simplified nutshell, polymorphism means an object can be many things at the same time. Because an object can extend another class and/or implement multiple interfaces, it shares their characteristics and can be used wherever a parent class or any implemented interfaces are called for. So for example, if an interface is specified as a method parameter or return type (as with the getTestStepList() method), it means that the method will take or return an object of a class that implements the given interface. The same principle applies to classes that are sub-classed: if a class is given as a parameter or return type, the method can actually take or return an instance of the class or any sub-class of that class. For example,WsdlTestStep
is the return type for the getTestStepAt() method. If you look at the javadoc entry for WsdlTestStep, you'll see that it has three sub-classes: WsdlPropertiesTestStep, WsdlRunTestCaseTestStep, and WsdlTestStepWithProperties (which itself has its own sub-classes). The specific class returned by the method, then, can be any of the sub-classes of WsdlTestStep (including the sub-classes of the WsdlTestStepWithProperties class).The problem with polymorphism in the context of javadocs is that they may not indicate the specific class that's ultimately returned by a call to the method in your script. Knowing the class of the returned object is important, of course, in order to know what methods and properties are available to that object. If push comes to shove, you can identify an object's class by using the getClass() method-- this is a basic method inherited by all classes from Java's Object class:
def myTestStep = testCase.getTestStepAt(1) log.info(myTestStep.getClass()) //Example output: class com.eviware.soapui.impl.wsdl.teststeps.WsdlGroovyScriptTestStep //The return type specified in the javadoc for getTestStepAt() is WsdlTestStep //The object returned is actually an instance of an indirect sub-class of the WsdlTestStep class