- The Problem of Encapsulation
- Accessing Packages and Protected Class Members
- Accessing Private Class Members
- Quick Quiz
- In Brief
Accessing Packages and Protected Class Members
We will start by demonstrating how to easily access package-visible variables and methods. Our example uses a package-visible variable, but the technique works equally well for protected visibility. A variable or method is package visible when no specific visibility keyword such as public, protected, or private is used for declaration. BorderLayout stores the component that was added using the BorderLayout.CENTER constraint in a center variable declared as follows:
package java.awt; public class BorderLayout implements LayoutManager2, java.io.Serializable { ... Component center; .... }
Recall that package-visible members are accessible to the class that declared them and all classes within the same package. In our example, any class in the java.awt package can access the center variable directly. A simple solution therefore is to create a helper class, AwtHelper, in the java.awt package and use it to access package-visible members of BorderLayout instances. AwtHelper has a public function that takes in an instance of BorderLayout and returns the component for a given layout constraint:
package java.awt; public class AwtHelper { public static Component getChild(BorderLayout layout, String key) { Component result = null; if (key == BorderLayout.NORTH) result = layout.north; else if (key == BorderLayout.SOUTH) result = layout.south; else if (key == BorderLayout.WEST) result = layout.west; else if (key == BorderLayout.EAST) result = layout.east; else if (key == BorderLayout.CENTER) result = layout.center; return result; } }
Let's write a test class called covertjava.visibility.PackageAccessTest that uses AwtHelper to obtain the split pane instance from Chat's MainFrame. The following source code excerpt is what we are mostly interested in:
Container container = createTestContainer(); if (container.getLayout() instanceof BorderLayout) { BorderLayout layout = (BorderLayout)container.getLayout(); Component center = AwtHelper.getChild(layout, BorderLayout.CENTER); System.out.println("Center component = " + center); }
We obtain the layout for the container and, if it is BorderLayout, we use AwtHelper to get the center component. Chat's MainFrame has the split pane in the center; therefore, if the code is written correctly, we should see an instance of JSplitPane on the system console. Running PackageAccessTest, we get the following exception:
java.lang.SecurityException: Prohibited package name: java.awt
The exception is thrown because java.awt is considered to be a system name space that should not be used by regular classes. This would not have happened if we were trying to hack a package-visible member of a third-party class, but we have intentionally picked a system class to illustrate a real-life example. The only potential problem with using this technique for a nonsystem name space such as com.mycompany.mypackage occurs if the package is sealed. Adding a helper class to a sealed package requires the same technique as is explained for adding a patched class in Chapter 5, "Replacing and Patching Application Classes."
Adding system classes is a little trickier because they are loaded and treated differently from application classes. Chapter 16, "Intercepting Control Flow," provides a comprehensive discussion of system classes. For now, though, it would suffice to say that to add a class to the system package, the class has to be placed on the boot class path. A directory or JAR file can be prepended or appended to the boot class path using the -Xbootclasspath parameter to the java command line. Because we already have a patches subdirectory for the Chat application, we will use it for system classes as well. We modify build.xml to move the java.lang directory with AwtHelper to distrib/patches and create a new script (package_access_test.bat) in distrib/bin, as follows:
@echo off set CLASSPATH=..\lib\chat.jar java -Xbootclasspath/p:..\patches covertjava.visibility.PackageAccessTest
Running package_access_test.bat produces the following output:
C:\Projects\CovertJava\distrib\bin>package_access_test.bat Testing package-visible access Center component = javax.swing.JSplitPane[,0,0,0x0,...]
Having to place classes on the system boot class path makes deployment a little more involved because it requires modification of the startup script. For example, a Web application that is deployed into a Web container, such as Tomcat or WebLogic, can no longer be simply deployed through a console or the application deployment directory. The script that starts the application server must be modified to include the -Xbootclasspath parameter. Another disadvantage of this technique is that it does not work for private members. Last, but not least, adding classes to packages can violate the license agreement. This is the case with BorderLayout because a section in Sun's Java license agreement explicitly prohibits adding classes to packages that begin with java. The next section presents another alternative that solves some of these problems.
Stories From the Trenches - WebCream is a product that converts Java AWT and Swing applications into interactive HTML Web sites. It does it by emulating a graphical environment for the graphical user interface (GUI) application running on the server side and capturing and converting the currently displayed top window to an HTML page. To generate the HTML, WebCream iterates all containers and tries to mimic Java layouts with HTML tables. One of the layouts WebCream needs to support is BorderLayout. For a container with BorderLayout, the HTML rendering module needs to know which child component has been added to the South section, which one to the North, and so on. BorderLayout stores this information in the member variables south, north, and so on, and it even has a getChild() method that can be used to obtain the component. The problem is that the variables are declared with package visibility and the getChild method is declared as private. To get around the absence of public access to BorderLayout's child components, WebCream engineers had to rely on the hacking techniques described in this chapter.