Taming Tiger

  • November 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Taming Tiger as PDF for free.

More details

  • Words: 6,714
  • Pages: 27
Taming Tiger, An introduction to Java 1.5 ..........................................................................2 Autoboxing and unboxing................................................................................................3 The enhanced for loop.....................................................................................................3 Variable method arguments and printf.............................................................................4 Enumerations...................................................................................................................5 Static imports...................................................................................................................8 Conclusion.......................................................................................................................9 Taming Tiger, Understanding generics................................................................................9 Why generics?................................................................................................................10 Enter Java generics........................................................................................................10 Diving into generics.......................................................................................................11 Using wildcards.............................................................................................................12 Roll your own parameterized type.................................................................................14 Generic methods............................................................................................................15 Conclusion.....................................................................................................................15 Taming Tiger, Java annotations .........................................................................................15 What is metadata?..........................................................................................................16 Built-in annotations........................................................................................................17 java.lang.Overrides....................................................................................................17 java.lang.annotation.Documented..............................................................................18 java.lang.annotation.Deprecated................................................................................18 java.lang.annotation.Inherited....................................................................................18 java.lang.annotation.Retention...................................................................................19 java.lang.annotation.Target........................................................................................19 Create a custom annotation............................................................................................20 Conclusion.....................................................................................................................27

Page 1 of 27

Taming Tiger, An introduction to Java 1.5 By Tarak Modi, JavaWorld.com, 04/26/04 Welcome to the first of a three-part series on Sun Microsystems' latest release of the Java 2 Platform, Standard Edition (J2SE). J2SE 1.5—code-named Tiger—is the most significant revision to the Java language since its origination. With version 1.5, Java has been extended with several new features, and my goal with this series is to introduce you to the most important features. Each article presents a detailed discussion of a handful of distinct features and provides practical examples of how they can be used in the real world. Part 1 is a quick introduction to J2SE 1.5 and covers many new additions. Part 2 will be devoted to generics, Java's counterpart to templates in C++ and a similar facility (also called generics) in C# 2.0. Part 3 focuses exclusively on J2SE 1.5's new metadata facility called annotations, which allow programmers to decorate Java code with their own attributes. These custom attributes can be used for code documentation, code generation, and, during runtime, to provide special services such as enhanced business-level security or special business logic. Read the whole series: "Taming Tiger," Tarak Modi (JavaWorld): •Part 1: An introduction to Java 1.5 •Part 2: Understanding generics •Part 3: Decorate your code with Java annotations

As I mentioned before, J2SE 1.5 represents the largest overhaul of Java since its inception and contains many new and exciting features. The additions provide developers with a host of new capabilities. For example, the addition of enumerations and generics provides Java with compile-time type safety. That means programming or logic errors are caught during program compilation instead of during runtime. This may not seem to be a great benefit on the surface, especially for smaller projects. However, as your code's complexity increases and your project grows larger, it becomes more likely that parts of a program may not have been unit tested (I know this never happens on your project!), and after three months of smooth sailing in production, you answer a call at 2 a.m. that informs you of a java.lang.ClassCastException. Compile-time type safety catches all these errors during program compilation.

Page 2 of 27

In this article, I discuss some of the new language features introduced in J2SE 1.5, including autoboxing and unboxing of primitives, the enhanced for loop, variable method arguments (varargs), the much anticipated enumerations, static imports, and the new formatted input method printf (a concept borrowed from C and C++). All of these features are part of Java Specification Request 201. Without further ado, let's start with the first feature—autoboxing and unboxing.

Autoboxing and unboxing Consider the following code fragment: // A list that stores numbers // Generics will make this obvious... List numbers = new ArrayList(); numbers.add(89);

Prior to version 1.5, this code would not compile since ArrayList's add method expects an object. To make it work, you must modify the second line as follows: numbers.add(new Integer(89));

That seems unnecessary and, with J2SE 1.5, it is. With J2SE 1.5, the compiler automatically adds the code to convert the integer value (such as 89) into the proper class (Integer in our example). That is called boxing. The opposite process of converting an object (such as of type Integer) into a value (such as an int) is called unboxing. Boxing and unboxing eliminate the (frequent and often burdensome) need to explicitly convert data of primitive type to reference and vice versa. Such explicit required conversions are verbose and induce clutter in the program text. The following table shows the rules for boxing and unboxing. Primitive type boolean byte double short int long float

Reference type Boolean Byte Double Short Integer Long Float

The enhanced for loop A common programming task is iterating over the elements of a collection or an array. An example is shown in the code fragment below:

Page 3 of 27

// numbers is a list of Numbers for (Iterator it = numbers.iterator(); it.hasNext(); ) { Integer number = (Integer) it.next(); // Do something with the number... }

The above code fragment, although effective, is cumbersome. Version 1.5 enhances the for statement to accept a more evolved syntax (the previous code still works) such as: for(Integer number: numbers) { // Do something with the number... }

The above code actually compiles into code similar to the first code fragment. The enhanced for loop also works with arrays. Personally, I would have preferred a new keyword such as "foreach." This is actually a FAQ, and Sun has responded to it by saying that creating a new keyword would be too "costly." Sun has, however, added keywords before, such as the assert keyword in 1.4 and the new enum keyword (we'll see this one shortly) in 1.5. So, I am not too convinced by this answer. Finally, remember that if you wish to modify the collection (such as remove elements) while traversing it, you cannot use the enhanced for loop.

Variable method arguments and printf Often, having a method that can operate on a variable number of parameters is convenient. A commonly accepted way of accomplishing that is to define the method so it accepts an array or collection of objects. An example code fragment is shown below: // An example method that takes a variable number of parameters int sum(Integer[] numbers) { int mysum = 0; for(int i: numbers) mysum += i; return mysum; } // Code fragment that calls the sum method sum(new Integer[] {12,13,20});

While this proves effective in simulating a method that can take a variable number of parameters, it seems clunky and involves slightly more (unnecessary) work by the programmer. Fortunately, that is no longer required in version 1.5 thanks to the new variable arguments (varargs) feature. Let's rewrite the above code fragments using varargs. // An example method that takes a variable number of parameters int sum(Integer... numbers)

Page 4 of 27

{

int mysum = 0; for(int i: numbers) mysum += i; return mysum;

} // Code fragment that calls the sum method sum(12,13,20);

Note the change in the method signature. Instead of explicitly taking an array of Integer objects, we inform the compiler that a variable number of Integer objects will be passed using an ellipsis (...). Note that the actual code inside the method does not change. Finally, notice how much cleaner (and simpler) the method call becomes. A perfect example of where this feature has been used by version 1.5 itself is in the implementation of the C-style formatted output method printf: // Using System.out.println and System.out.printf int x = 5; int y = 6; int sum = x + y; // Pre 1.5 print statement System.out.println(x + " + " + y + " = " + sum); // Using 1.5 printf System.out.printf("%d + %d = %d\n", x, y, sum);

Both statements print the same line to the console—5 + 6 = 11. You can do a lot more with the extremely powerful and versatile printf method. Look at the following code fragment: // Demonstrating printf versatility System.out.printf("%02d + %02d = %02d\n", x, y, sum);

This line of code ensures that each number prints as two digits and, if the number is a single digit, then a zero (0) precedes it. So the output on the console now looks as follows: 05 + 06 = 11.

Enumerations Consider the following class: public Class Color { public static int Red = 1; public static int White = 2; public static int Blue = 3; }

You could use the class as shown in the following code fragment: int myColor = Color.Red;

Page 5 of 27

But, what is to prevent someone from doing the following? int myColor = 999;

Obviously the compiler will not (and cannot) catch this error, which quite possibly will lead to a runtime exception. Here's an alternative implementation of the Color class that solves the problem mentioned above: // The new Color class public class Color { // Color value; int _color; // Constructor protected Color (int color) { _color = color; } public static final int _Red = 1; public static final int _White = 2; public static final int _Blue = 3; public static final Color Red = new Color(_Red); public static final Color White = new Color(_White); public static final Color Blue = new Color(_Blue); }

And here is how you would use it: Color myColor = Color.Red;

The above implementation ensures that invalid colors cannot be specified. However, we did a lot of work and our solution remains limited. J2SE 1.5 provides a better solution with its addition of enumerations; a concept C and C++ programmers are already familiar with. Let's rewrite the Color class using enumerations: // The color enumeration public enum Color { Red, White, Blue }

Notice the usage of the keyword enum instead of class. Also, notice how simple the implementation has become. Here's how you could use the Color enumeration in your code: // Using the Color enumeration Color myColor = Color.Red;

Page 6 of 27

The usage is the same as the usage of our second example (above), but the enumeration is much more capable than our simple Color class implementation. For example, all enumerations have a static method called values() that returns all the enumeration values in a collection as shown in the code fragment below: // Cycling through the values of an enumeration for (Color c : Color.values()) System.out.println(c);

The above code fragment produces the following output: Red White Blue

All enumerations also have another static method called valueOf() that returns the enumeration constant corresponding to a string parameter. For example Color.valueOf("Red") returns Color.Red. In addition, each enumeration has several useful instance methods. The name() method returns the enumeration constant's name, exactly as declared in its declaration. For example, Color.Red.name() returns Red. The toString() method returns the same value as name(), but can be overridden to provide a better and more friendlier or meaningful (and even internationalized) name. The ordinal() method returns the zerobased position of the declared constant. For example, Color.White.ordinal() returns 1. Finally, the compareTo() method compares the current enumeration object (i.e., this) with the specified enumeration object and returns a negative integer, zero, or a positive integer, depending on whether the current enumeration object is less than, equal to, or greater than the specified object, respectively. The comparison is based on the ordinal of the declared enumeration constants. For example, Color.Red.compareTo(Color.White) will return -1. Enumerations can have constructors and methods as well. For example, let's say I wanted to provide a friendlier name than offered by the default toString() implementation. Here's an example that accomplishes that: // The revised color enumeration public enum Color { Red("Red (#FF0000)"), White("White (#000000)"), Blue("Blue (#0000FF)"), Green("Green (#00FF00)") { public boolean isInUSFlag() { return false;

Page 7 of 27

};

}

private String newName;

}

Color(String s) { newName = s; } public String toString() { return newName; } public boolean isInUSFlag() { return true; }

Note that I also added a new color, Green, and a new method called isInUSFlag() that by default returns true. Since green is not in the US flag, I override the isInUSFlag() method when I declare the constant Green and return false from the overridden method implementation.

Static imports The last new language feature explained in this article is the static import. Static imports are yet another convenience feature added to version 1.5 that extends the way imports work in Java. For example, consider the code fragment shown below that calls the static ceil() method on the java.lang.Math class // x is a number of type double such as 5.345 double y = Math.ceil(x);

With static imports in 1.5, you can ask the Java compiler to import only a class's static portions, as shown, for example, in the rewritten code fragment below: // Import declaration at the top of the class along with the other imports import static java.lang.Math.ceil; // And then somewhere in the code... // x is a number of type double such as 5.345 double y = ceil(x);

In the above fragment, I used the new static import feature to import the static method ceil() from the Math class. Now when I call the method, I don't have to qualify it with Math. If I wanted to use multiple static methods from the Math class, I could import them individually or all at once as shown below: Page 8 of 27

// Import all static methods from Math import static java.lang.Math.*;

This also applies to any constants declared within Math, such as E and PI. With the above declaration, I can use these constants as if they were declared locally within my class (or superclass). Finally, static imports can be used with enumerations as well. For example, consider the following enumeration definition: package myEnumerations; public enum Color { Red, White, Blue }

You may access this enumeration's values in (at least) one of the following two ways: Import myEnumerations.Color and reference Color.Red in your code Statically import myEnumerations.Color.* and reference Red in your code

Conclusion This article has provided a quick introduction to a few of the new language features in J2SE 1.5. To be completely honest, none of these features introduce any groundbreaking or previously impossible functionality into the Java language. As we saw throughout the article, there were always workarounds. What the new features do provide are reduced code complexity, enhanced readability, and much better compile-time type safety. However, if I had to choose one feature that most impresses me, I would select enumerations. In my opinion, Sun and the Java team have done an excellent job with its implementation of enumerations by making it extremely close to a first-class class within Java, thus enabling enumerations to have constructors, methods, and method overriding. In Part 2 this series, I will go into detail about the newly introduced templating feature in J2SE 1.5 called generics. Generics are very exciting and allow you to take compile-time type safety to yet another level. Also, unlike the features discussed in this article, generics do introduce groundbreaking functionality in Java.

Taming Tiger, Understanding generics By Tarak Modi, JavaWorld.com, 06/07/04

Page 9 of 27

Welcome to the second part of this three-part series on Sun Microsystems' latest release of the Java 2 Platform, Standard Edition (J2SE). To refresh your memory, Part 1 was a quick introduction to J2SE 1.5 and covered many new additions to the Java language: auto-boxing and -unboxing of primitives, enumerations, static imports, the enhanced "for" loop, variable method arguments, and the newly formatted input method. I devote Part 2 entirely to generics, Java's counterpart to templates in C++ and a similar facility (also called generics) in C# 2.0. Read the whole series: "Taming Tiger," Tarak Modi (JavaWorld): •Part 1: An introduction to Java 1.5 •Part 2: Understanding generics •Part 3: Decorate your code with Java annotations

Why generics? To understand the problem that generics (short for generic types) solve, let's look at the following code fragment: // Declare Class A class A { } // Declare Class B class B { } // Somewhere in the program create a Vector Vector v = new Vector(); // Add an object of type A v.add(new A()); // And sometime later get the object back B b = (B) v.get(0);

The above code will compile fine but will throw a runtime exception (java.lang.ClassCastException) when you execute it. This is obviously a serious problem and should be caught as early as possible. Preferably, the above code fragment should not even compile.

Enter Java generics Now, let's rewrite the above code fragment using generics: // Class A and B declared previously // Somewhere in the program create a Vector Vector v = new Vector(); // Add an object of type A v.add(new A()); // And sometime later get the object back

Page 10 of 27

B b = (B) v.get(0);

That looks similar to the first fragment, except for the code in the angle brackets. The angle brackets are the syntax for providing type parameters to parameterized types. I talk more about both parameterized types and type parameters in this article's later sections. Even though the code change is minimal, its impact is far from small. This code fragment will not compile. Compiling this program with J2SE 1.5 (remember to use -source 1.5) will give you the following error: inconvertible types found : A required: B B b = (B) v.get(0);

In plain English, the compiler tells us that to be able to use the line B b = (B) v.get(0); legally, the vector must be parameterized (in the declaration) to accept objects of type B.

Diving into generics Generics were first proposed for Java in early 2001, and (after three long years) they have finally made their way into J2SE 1.5. As you saw above, generics provide a giant leap into solving a long-standing problem in Java; implementing compile-time type safety. One of the first places you will encounter generics in J2SE 1.5 (and in C++ and C#) are in the collection classes, such as the Vector class we used above. Such classes are called parameterized types. If a type is parameterized, then its declaration (i.e., when you declare an instance of that type) can optionally take a type with which to parameterize it. For example, we already know that Vector is a parameterized type, and hence, the declaration of v (in the example above) is as follows: Vector
v = new Vector();

The type is optional to maintain backwards compatibility with legacy code. If the type were mandatory, then any code with a declaration such as Vector v = new Vector(); would no longer compile. Once a parameterized type is declared, using it is no different than a regular (pre 1.5) nonparameterized type. There are subtleties to keep in mind while using generics. Consider the following seemingly harmless code fragment: // Somewhere in the program create a Vector Vector<String> v2 = new Vector<String>(); Vector v = v2;

Page 11 of 27

Compile the code and run it. What? It won't compile. Even though Object is the base class of all classes including String, you cannot assign a vector of type String to a vector of type Object. The converse is also true and is more intuitive than the former. The reason such rules exist are to ensure compile-time type safety and to prevent errors such as the following: // Somewhere in the program create a Vector Vector<String> v2 = new Vector<String>(); Vector v = v2; v.add(new A()); String s = v2.get(0);

Assuming that J2SE 1.5 relaxed its assignment rules, the above code would compile, but would result in a runtime exception when the line String s = v2.get(0) executes.

Using wildcards To understand what wildcards are and why we need them, consider the following code fragment: // Declare a Mammal class abstract class Mammal { abstract public void eat(); } // Declare a Dog class class Dog extends Mammal { public void eat() { System.out.println("Dog is eating."); } } // Declare a Cat class class Cat extends Mammal { public void eat() { System.out.println("Cat is eating."); } } // Somewhere in the code create a vector of dogs and cats Vector dogs = new Vector(); dogs.add(new Dog()); dogs.add(new Dog()); Vector cats = new Vector(); cats.add(new Cat()); cats.add(new Cat()); cats.add(new Cat()); // Now make all the dogs and cats eat Vector<Mammal> pets = dogs; for(Mammal pet: pets) {

Page 12 of 27

pet.eat(); } pets = cats; for(Mammal pet: pets) { pet.eat(); }

By now we already know that this code when compiled will produce the following errors: incompatible types found : java.util.Vector required: java.util.Vector<Mammal> Vector<Mammal> pets = dogs; ^ incompatible types found : java.util.Vector required: java.util.Vector<Mammal> pets = cats;

But what if I wanted to make this code work? After all, it is legitimate—albeit contrived —code. The answer is simple and involves using wildcards. Here's the one line change that will appease the Java compiler: Vector pets = dogs;

Now after you successfully compile and run the code, you will receive the following output: Dog Dog Cat Cat Cat

is is is is is

eating. eating. eating. eating. eating.

So how did that work? The ? is a wildcard character that, along with the extends keyword, declares pets as a Vector of any type that derives from Mammal. That differs from the line we wrote before that declared pets as a Vector that could only contain objects of the direct type Mammal. To accept a Vector of any kind of object you could use the following declaration: Vector

Note: That code differs from Vector for the same reasons discussed above.

Page 13 of 27

Roll your own parameterized type So far we've used the built-in parameterized types in Java. Now let's look at the other side of the coin—creating parameterized types. It's fairly easy to create your own parameterized types. As an example, let's create a new class called Mammals that can store any Mammal: class Mammals { private Vector items = new Vector(); void add(T item) { items.add(item); } T get(int index) { return items.get(index); } }

Now you can use this new class in your code, as shown in the following fragment: // A list of dogs Mammals dogs2 = new Mammals(); dogs2.add(new Dog()); dogs2.add(new Dog()); // A list of cats Mammals cats2 = new Mammals(); cats2.add(new Cat()); cats2.add(new Cat()); cats2.add(new Cat()); // Assign the dogs to a generic list of mammals Mammals mammals = dogs2;

To support iterating through the Mammals list, I modify the class as follows: class Mammals<E extends Mammal> extends Vector<E> { }

No, I did not forget the code in the class. I extended the Vector class and inherited all the required functionality (Isn't that what object-oriented programming is all about?). Now we can iterate through the list, as shown below: Mammals mammals = dogs2; for (Mammal m: mammals) { m.eat(); } mammals = cats2;

Page 14 of 27

for (Mammal m: mammals) { m.eat(); }

Generic methods J2SE 1.5 supports generic methods. To see how such methods can be useful, let's say you wanted to create a method that took an array of Mammals and put them in a Mammals list. Here's one way of accomplishing that: // Mammals class from earlier example class Mammals<E extends Mammal> extends Vector<E> { } // A generic method static void arrayToList(T[] array, Mammals list) { for(T item: array) list.add(item); }

Generic methods prove especially useful when new functionality to an existing parameterized class is needed and it is not possible to extend the class, or when a group of such methods are provided as common utility methods.

Conclusion I talked at length about generics in this article. Generics are a strong addition to Java and provide a level compile-time type safety not possible in previous versions of the language. They are also used extensively within J2SE 1.5. In addition, not surprisingly, Reflection in J2SE 1.5 has also been extended to support reflecting on generic types. However, those details reach beyond the scope of this introductory article. Perhaps, if there is enough interest, I will write an article exclusively on using Reflection on generics (and a potential weakness with Java's generics implementation called erasure). In the third and final part of this series, I will go into details about the newly introduced metadata feature in J2SE 1.5 called annotations. Annotations allow programmers to decorate Java code with their own attributes that can be used for code documentation, code generation, and, during runtime, for providing special services such as enhanced business-level security or special business logic.

Taming Tiger, Java annotations By Tarak Modi, JavaWorld.com, 07/19/04

Page 15 of 27

Welcome to the third and final part of this three-part series on Sun Microsystems' latest release of the Java 2 Platform, Standard Edition (J2SE). As I mentioned in Part 1, J2SE 5 (also called Tiger) is the most significant revision to the Java language since its original inception and has extended Java with several new features. To refresh your memory, Part 1 provided a brief introduction to J2SE 5 and covered many new additions to the Java language. Part 2 was devoted entirely to generics, which are Java's counterpart to templates in C++ and a similar facility (also called generics) in C# 2.0. In this final installment, I focus exclusively on the newly introduced metadata feature in J2SE 5 called annotations. Annotations allow programmers to decorate Java code with their own attributes. These attributes can be used for code documentation, code generation, and, even during runtime, for providing special services such as enhanced business-level security or special business logic. Read the whole series: "Taming Tiger," Tarak Modi (JavaWorld): •Part 1: An introduction to Java 1.5 •Part 2: Understanding generics •Part 3: Decorate your code with Java annotations

What is metadata? If you look up the definition of metadata, the dictionary will tell you it's data about data. The same definition applies to metadata in Java as well. Metadata is not a completely foreign concept to Java. Java has always supported a (very) limited implementation of metadata via its javadoc tags. An example code fragment follows: /** * @author Tarak Modi * */ public final class AnnotationsTest { }

In the above code fragment, the @author tag is an example of metadata for the AnnotationsTest class. In other words, AnnotationsTest is annotated or decorated by the author metadata. Currently, this metadata is only used by tools such as javadoc and is not even available during runtime. XDoclet, an open source project, is another example that uses javadoc-style metadata tags to annotate and generate code based on the metadata. With J2SE 5, the Java team has taken Java's metadata capability to a new level. The concept is formally called annotations. In addition to the built-in annotations included in Version 5, you can also use custom annotations that you define to decorate types (such as classes, interfaces, and enums), methods and constructors, and fields. You can control the availability of these annotations as well. For example, some annotations may be available Page 16 of 27

only in the source code; others may be available in the compiled class files as well. Some annotations may even be available during runtime in the JVM.

Built-in annotations J2SE 5 comes with six prebuilt annotations. Each annotation is described below. The annotation's actual definition is also provided in the reference's description. Two points to note from the definitions are their simplicity and that the annotations themselves are annotated. Later in this article, I walk you through a complete example of building and using your own annotation; during that discussion, I explain the declaration's syntax. The following are the six built-in annotations in J2SE 5:

java.lang.Overrides @Target(ElementType.METHOD) public @interface Overrides { }

This annotation is used to indicate that a method declaration in the current class is intended to override a method declaration in its (direct or indirect) superclass. If a method is decorated with this annotation type but does not override a superclass method, then the compiler will generate an error message. As an example, consider the following code fragment: class A { @Overrides public String toString(int i) { return ""; } }

Compiling this code fragment produces the following error during compilation: method does not override a method from its superclass @Overrides ^

To fix the error, change the method signature from public String toString(int i) to public String toString(). Using this annotation is a good way to catch inadvertent programming errors during compilation, where you think you are overriding a method, but in reality you are creating a new one.

Page 17 of 27

java.lang.annotation.Documented @Documented @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }

This annotation indicates that the annotation to which this is applied is to be documented by javadoc and similar tools. Note that this annotation is just a hint, and a tool can ignore the annotation if it desires to do so.

java.lang.annotation.Deprecated @Documented @Retention(RetentionPolicy.SOURCE) public @interface Deprecated { }

This annotation provides a hint to the Java compiler to warn users if they use the class, method, or field annotated with this annotation. Typically, programmers are discouraged from using a deprecated method (or class), because either it is dangerous or a better alternative exists. As an example of when you could use the Deprecated annotation, consider a scenario where you have implemented a sort() method on a class used by many other developers on different projects. Later you discover a shortcoming in the code and not only have to rewrite the function, but also change its signature. You can't remove the old sort() method because other programs rely on it. What you can do is annotate it as being deprecated so when developers work on and compile their code, they will be advised to use the new sort() method instead of the old one.

java.lang.annotation.Inherited @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }

The best way to understand this annotation is through an example. Let's say you created your own annotation called Serializable. A developer would use your annotation if his class implemented the Serializable interface. If the developer created any new classes that derived from his original class, then marking those classes as Serializable would make sense. To force that design principal, you decorate your Serializable annotation Page 18 of 27

with the Inherited annotation. Stated more generally, if an annotation A is decorated with the Inherited annotation, then all subclasses of a class decorated by the annotation A will automatically inherit the annotation A as well.

java.lang.annotation.Retention @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }

The Retention annotation takes a single parameter that determines the decorated annotation's availability. The values of this parameter are: RetentionPolicy.SOURCE: The

decorated annotation is available at the source code

level only RetentionPolicy.CLASS: The

decorated annotation is available in the source code and compiled class file, but is not loaded into the JVM at runtime

RetentionPolicy.RUNTIME: The

decorated annotation is available in the source code, the compiled class file, and is also loaded into the JVM at runtime

By default, all annotations are available at the source level only.

java.lang.annotation.Target @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }

This annotation is used to indicate the type of program element (such as a class, method, or field) to which the declared annotation is applicable. If the Target annotation is not present on an annotation type declaration, then the declared type may be used on any program element. If the Target annotation is present, then the compiler will enforce the specified usage restriction. Legal values for the Target annotation are contained in the java.lang.annotation.ElementType enumeration. An annotation can be applied to any combination of a class, a method, a field, a package declaration, a constructor, a parameter, a local variable, and another annotation.

Page 19 of 27

Create a custom annotation

It's easy to create and then use a custom annotation. As you have already seen above, the declaration of an annotation is straightforward. In this section, I walk you through a complete exercise of creating, applying, and dynamically querying a custom annotation during runtime. The custom annotation Let's create an annotation that can be applied to classes and methods and enforces rolebased security. The declaration is shown below: @Documented @Target({METHOD,TYPE}) @Retention(RUNTIME) public @interface SecurityPermission { String[] value(); }

We can glean the following information from the declaration: The annotation is to be documented by tools such as javadoc. The annotation can be applied only to methods and types such as classes. The annotation should be available during runtime. The annotation is called SecurityPermission. The annotation has one member called value and is an array of string. Note that we don't declare the actual member, only an accessor method to retrieve it.

The next step is declaring a class that uses our new annotation. An example is shown below: @SecurityPermission("All") public class AnnotationsTest { public AnnotationsTest() { SecurityBlanket.checkPermission(); } @SecurityPermission("All") public void unRestrictedMethod(String message) { SecurityBlanket.checkPermission(); System.out.println("Message from unRestrictedMethod: " + message); } @SecurityPermission("None")

Page 20 of 27

public void fullyRestrictedMethod(String message) { SecurityBlanket.checkPermission(); System.out.println("Message from fullyRestrictedMethod: " + message); } @SecurityPermission("Manager") public void partiallyRestrictedMethod(String message) { SecurityBlanket.checkPermission(); System.out.println("Message from partiallyRestrictedMethod: " + message); } @SecurityPermission({"Manager","HR"}) public void partiallyRestrictedMethod2(String message) { SecurityBlanket.checkPermission(); System.out.println("Message from partiallyRestrictedMethod2: " + message); } }

As you can see, AnnotationsTest is a class just like any other with special javadoc-like tags (@SecurityPermission) above the class and method declarations. This is how you apply the SecurityPermission annotation. Note that this is also the same way you apply the built-in annotations to Java code, such as, for example, to the SecurityPermission annotation declaration. Next, look at the various SecurityPermission annotation declarations to see how I specified the value parameter's value. As you peruse through the AnnotationsTest class above, you may wonder what the call SecurityBlanket.checkPermission() is doing. This is where all the "magic" occurs. Simply declaring a custom annotation and then decorating your code with it is not going to accomplish much. The Java runtime has no idea what to do with your custom annotations. They just occupy valuable memory space doing nothing. That's where the SecurityBlanket.checkPermission() method call fits in. First, this function figures out what the currently executing method is. Next, the function determines if the caller's role is one of the permitted roles for the method. If the method is a class constructor, then the function uses the roles specified in the SecurityPermission annotation applied to the class declaration; otherwise it uses the roles specified in the SecurityPermission annotation applied to the method declaration. The caller's roles are specified on a per-thread basis by calling the SecurityBlanket.addPermission method. In a real application, a custom JAAS (Java Authentication and Authorization Specification) module that allows a user to log into your application may call the SecurityBlanket.addPermission method on your behalf. The point to realize from all this is that J2SE 5 provides us with a way of declaring our own custom annotations and a way of decorating our own code with them. It also loads them in the JVM and makes them available dynamically and reflectively. But then we have to write the glue logic that actually makes something useful happen. Page 21 of 27

The entire SecurityBlanket class is shown below: public class SecurityBlanket { private static Logger logger = Logger.getLogger("AnnotationsTest"); private static HashMap permissions = new HashMap(); public static void addPermission(String s) { permissions.put(Thread.currentThread(),s); } public static void removePermission() { permissions.remove(Thread.currentThread()); } public static void checkPermission() { StackTraceElement e[]=Thread.currentThread().getStackTrace(); int i = 0; for (i = 0; i <e.length; i++) { if (e[i].getClassName().equals("SecurityBlanket") && e[i].getMethodName().equals("checkPermission")) break; } if (i == e.length) { throw new RuntimeException("Unexpected Security Error."); } logger.info("Checking security access to " + e[i+1].getClassName() + "::" + e[i+1].getMethodName()); try { Class c = Class.forName(e[i+1].getClassName()); if (e[i+1].getMethodName().equals("")) { SecurityPermission permission = (SecurityPermission) c.getAnnotation(SecurityPermission.class); if (permission != null) { String currentRole = permissions.get(Thread.currentThread()); for (String role:permission.value()) { if (role.equals("All")) return; else if (role.equals("None")) { throw new RuntimeException( "Unauthorized access to class " + e[i+1].getClassName()); } if (role.equals(currentRole)) return;

Page 22 of 27

} } return;

} Method[] methods = c.getMethods(); for (Method m:methods) { if (m.getName().equals(e[i+1].getMethodName())) { SecurityPermission permission = m.getAnnotation(SecurityPermission.class); if (permission != null) { String currentRole = permissions.get(Thread.currentThread()); for (String role:permission.value()) { if (role.equals("All")) return; else if (role.equals("None")) { throw new RuntimeException( "Unauthorized access to " + e[i+1].getClassName() + "::" + e[i+1].getMethodName()); } if (role.equals(currentRole)) return; } } break; } } throw new RuntimeException("Unauthorized access to " + e[i+1].getClassName() + "::" + e[i+1].getMethodName()); } catch (ClassNotFoundException ex) { logger.severe("Got an error: " + ex.getMessage()); throw new RuntimeException("Unexpected Security Error."); } }

}

The bulk of the code is straightforward. However, note the use of the getAnnotation() method on the java.lang.reflect.Method and the java.lang.Class classes. These are just two of the many new methods added to support reflectively retrieving annotations during runtime.

Page 23 of 27

Finally, to see everything in action, let's add a main() method to the AnnotationsTest class. The main() method creates two threads. Each thread simulates a different user with a different role and calls all four public instance methods in the AnnotationsTest class. The main method is shown below: public static void main(String[] args) { try { Thread t1 = new Thread(new Test1()); t1.start(); t1.join(); Thread t2 = new Thread(new Test2()); t2.start(); } catch (Exception ex) { ex.printStackTrace(); } }

And here are the Test1 and Test2 classes used by the main method: class Test1 implements Runnable { public void run() { // Add the "HR" role for this user SecurityBlanket.addPermission("HR"); try { AnnotationsTest test = new AnnotationsTest(); try { test.unRestrictedMethod("Hi"); } catch (Exception ex) { System.out.println(ex.getMessage()); } try { test.fullyRestrictedMethod("Hi"); } catch (Exception ex) { System.out.println(ex.getMessage()); } try { test.partiallyRestrictedMethod("Hi"); } catch (Exception ex) {

Page 24 of 27

} try {

System.out.println(ex.getMessage());

test.partiallyRestrictedMethod2("Hi"); } catch (Exception ex) { System.out.println(ex.getMessage()); } } finally { SecurityBlanket.removePermission(); }

} } class Test2 implements Runnable { public void run() { // Add the "Manager" role for this user SecurityBlanket.addPermission("Manager"); try { AnnotationsTest test = new AnnotationsTest(); try { test.unRestrictedMethod("Hi"); } catch (Exception ex) { System.out.println(ex.getMessage()); } try { test.fullyRestrictedMethod("Hi"); } catch (Exception ex) { System.out.println(ex.getMessage()); } try { test.partiallyRestrictedMethod("Hi"); } catch (Exception ex) { System.out.println(ex.getMessage()); } try { test.partiallyRestrictedMethod2("Hi"); } catch (Exception ex) { System.out.println(ex.getMessage());

Page 25 of 27

} } finally { SecurityBlanket.removePermission(); } }

}

When you run the AnnotationsTest class, you will see the following output: Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest:: Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::unRestrictedMethod Message from unRestrictedMethod: Hi Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::fullyRestrictedMethod Unauthorized access to AnnotationsTest::fullyRestrictedMethod Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::partiallyRestrictedMethod Unauthorized access to AnnotationsTest::partiallyRestrictedMethod Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::partiallyRestrictedMethod2 Message from partiallyRestrictedMethod2: Hi Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest:: Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::unRestrictedMethod Message from unRestrictedMethod: Hi Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::fullyRestrictedMethod Unauthorized access to AnnotationsTest::fullyRestrictedMethod Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::partiallyRestrictedMethod Message from partiallyRestrictedMethod: Hi Mar 20, 2004 4:46:55 PM SecurityBlanket checkPermission INFO: Checking security access to AnnotationsTest::partiallyRestrictedMethod2 Message from partiallyRestrictedMethod2: Hi

Observe the following in the output: The first thread (role set to "HR") has access only to the unRestrictedMethod() and partiallyRestrictedMethod2() methods. The second thread (role set to "Manager") has access to the unRestrictedMethod(), partiallyRestrictedMethod(), and partiallyRestrictedMethod2() methods. Page 26 of 27

Neither thread has access to the fullyRestrictedMethod() method. Both threads can access the unRestrictedMethod() method. Both threads were able to create an instance of the AnnotationsTest class because the class was annotated with the SecurityPermission annotation SecurityPermission("All"). If All was changed to None, then neither thread could have created an instance of the class.

The best way to see what's going on is to play around with the code and change the values of the various SecurityPermission annotations that are sprinkled around the AnnotationsTest class. Finally, an awesome exercise would be to use AspectJ to define and implement the appropriate pointcuts that would allow you to completely remove the manual call to SecurityBlanket.checkPermission() from your code. Now that would be real "magic," and AspectJ makes it easy.

Conclusion This concludes my three-part series on J2SE 5. Along the way, I have covered many interesting new language features, some more useful than others. I talked at length about the new Java metadata facility called annotations in this article. Annotations provide us with a host of new capabilities. Expect to see many tools in the next couple of years that capitalize on Java annotations to provide new value-added services either at compile time by code modification or at runtime.

Page 27 of 27

Related Documents

Taming Tiger
November 2019 5
Tiger
July 2020 19
Tiger
November 2019 34
Taming Temptation
November 2019 23
Tiger
April 2020 30
Taming Risk
May 2020 7