GNU   davin.50webs.com/research
Bringing to you notes for the ages

       Main Menu          Research Projects         Photo Album            Curriculum Vitae      The Greatest Artists
    Email Address       Computer Games          Web Design          Java Training Wheels      The Fly (A Story)   
  Political Activism   Scruff the Cat       My Life Story          Smoking Cessation          Other Links      
Debugging Macros     String Class I     Linked List System I Java for C Programmers Naming Convention
    String Class II         How I use m4              Strings III                 Symmetrical I/O             Linked Lists II     
Run-Time Type Info   Virtual Methods      An Array System        Science & Religion            Submodes       
  Nested Packages      Memory Leaks    Garbage Collection      Internet & Poverty      What is Knowledge?
Limits of Evolution   Emacs Additions      Function Plotter           Romantic Love        The Next Big Thing
    Science Fiction     Faster Compilation Theory of Morality         Elisp Scoping               Elisp Advice      
  S.O.G.M. Pattern       Safe Properties         School Bullying          Charisma Control          Life and Death    
     Splitting Java          Multiple Ctors       Religious Beliefs         Conversation 1           Conversation 2    
   J.T.W. Language    Emacs Additions II      Build Counter             Relation Plotter          Lisp++ Language  
  Memory Leaks II   Super Constructors CRUD Implementation Order a Website Form There Is An Afterlife
More Occam's Razor C to Java Translator Theory of Morality II


Nested packages in Java Free Stuff

Abstract

The title of this article is the outcome of a discussion with Warwick Irwin of the University of Canterbury Department of Computer Science. This article investigates how the visibility status of Java package visible methods, properties and classes could be altered to reflect more accurately their nesting positions in the hierarchy of packages. This would make Java's package visibility modifier almost as general as equivalent notion in C++ of friendship without the downsides of C++ friendship. Two design patterns are presented that achieve almost all of this more general notion of package visibility without having to re-engineer the Java language. I gave a talk about this article to the IEE Short Papers Evening competition but I failed to secure any winning place in the competition.

1. The proposed modification

Given a general operator ~ on C++ and Java classes here are three properties that ~ can satisfy:


Reflexive A ~ A is true for all classes A
Symmetric A ~ B implies B ~ A for all classes A and B
Transitive A ~ B and B ~ C implies A ~ C for all classes A, B and C.


The C++ Friendship Operator, henceforth known as ~C++ is reflexive but not symmetric or transitive because classes are friends with themselves is the only thing you can say about a C++ program without actually looking at the source code to see what class is friends with what. Before we can look at the corresponding operator in Java, we need to look at a sample diagram of the Java source files and directories of a sample Java project. In the following diagram, red dots represent Java source files and green boxes represent directories:

The following graph shows how the classes of the above diagram relate in terms of the Java Package Field Visibility Operator, henceforth known as ~Java. Importantly, we are looking at package visibility applied to fields (methods and properties) but not to classes. Therefore we assume that all classes are declared with public visibility and not package visibility. As shown below, each distinct package is an island into itself because classes in subdirectories cannot access package visible fields in parent directories.

The ~Java operator is reflexive, symmetric and transitive so it is less general than ~C++. Here is a possible way that ~Java operator could be generalised:

The Generalised Java Can Access Operator, henceforth referred to as ~GJ is reflexive, weakly symmetric and transitive. By weakly symmetric I mean A ~GJ B implies B ~GJ A only when A and B are in the exact same package. The ~GJ operator gives rise to Augmented Trees. By an Augmented Tree I mean a tree augmented into a directed graph in such a way that if A ~GJ B and B ~GJ C then A ~GJ C is in the graph.


The operators discussed above lie in the following order from least general to most general: ~Java, ~GJ, ~C++ as the properties of ~GJ are a strict subset of the properties of ~Java and the properties of ~C++ are a strict subset of the properties of ~GJ. Therefore changing the way that Java package visibility is implemented from ~Java to ~GJ would make the language more expressive and closer to the expressiveness of C++.


There are two downsides to friendship in C++. The first downside is that to achieve a complicated friendship arrangement, many friend declarations are needed. The second downside is that to achieve a complicated friendship arrangement many source files need to be touched (i.e. modified) by having friend declarations added to them. The extra touching of files has poor ramifications for programs like make, which generally recompiles files that include files that have been touched since the last call to make. Even with Java modified in the way previously discussed, it would still be superior to C++ in respect of not needing any friend declarations.

2. The present system

Consider two Java source files. Here is the first file:
package alpha;

public class A
{
   ////////////////////////////////////////////////////////////////////
   ///
   ///                       PROPERTIES:
   ///
   ////////////////////////////////////////////////////////////////////

   public           int property1;
   protected        int property2;
                    int property3;
   private          int property4;
   public static    int property5;
   protected static int property6;
   static           int property7;
   private static   int property8;

   ////////////////////////////////////////////////////////////////////
   ///
   ///                         METHODS:
   ///
   ////////////////////////////////////////////////////////////////////

   public           void method1() { System.out.println("A's method1"); }
   protected        void method2() { System.out.println("A's method2"); }
                    void method3() { System.out.println("A's method3"); }
   private          void method4() { System.out.println("A's method4"); }
   public static    void method5() { System.out.println("A's method5"); }
   protected static void method6() { System.out.println("A's method6"); }
   static           void method7() { System.out.println("A's method7"); }
   private static   void method8() { System.out.println("A's method8"); }


   ////////////////////////////////////////////////////////////////////
   ///
   ///                       MAIN METHOD:
   ///
   ////////////////////////////////////////////////////////////////////

   public static void main(String args)
   {
      A a = new A();
      alpha.beta.B b = new alpha.beta.B();
      b.wibble(a);
   }
}

Here is the second file:
package alpha.beta;

public class B
{
   public void wibble(alpha.A a)
   {
      // Compiler error: method3() is not public in alpha.A;
      // cannot be accessed from outside package
      a.method3(); // ***

      // Compiler error: method7() is not public in alpha.A;
      // cannot be accessed from outside package
      a.method7(); // ***

      // Compiler error: property3 is not public in alpha.A;
      // cannot be accessed from outside package
      a.property3++; // ***

      // Compiler error: property7 is not public in alpha.A;
      // cannot be accessed from outside package
      a.property7++; // ***
   }
}

Compiling the second file gives an error at the lines marked *** because the package alpha.beta is considered to be different to the package alpha. This is despite the fact that the second file is stored in a subdirectory of the directory that the first file is stored in. It is natural to follow the analogy established by the directory structure and to think of package alpha.beta as being inside package alpha. Taking this idea to its limit we would expect that code in package alpha.beta could access methods and properties in package alpha which have package visibility. What follows is a design pattern that allows one to get around this flaw so that sub-packages can access methods and properties from parent packages.

3. A design pattern to make package visibility more general

To bypass this flaw in Java, and achieve a different visibility system the first file needs to be split into two parts. Here is the first part:

package alpha;

public class AInner
{
   ////////////////////////////////////////////////////////////////////
   ///
   ///                   PROPERTIES:
   ///
   ////////////////////////////////////////////////////////////////////

   public        int property1;
   public        int property2;
   public        int property3;
   public        int property4;
   public static int property5;
   public static int property6;
   public static int property7;
   public static int property8;

   ////////////////////////////////////////////////////////////////////
   ///
   ///                   METHODS:
   ///
   ////////////////////////////////////////////////////////////////////

   public        void method1() { System.out.println("AInner's method1"); }
   public        void method2() { System.out.println("AInner's method2"); }
   public        void method3() { System.out.println("AInner's method3"); }
   public        void method4() { System.out.println("AInner's method4"); }
   public static void method5() { System.out.println("AInner's method5"); }
   public static void method6() { System.out.println("AInner's method6"); }
   public static void method7() { System.out.println("AInner's method7"); }
   public static void method8() { System.out.println("AInner's method8"); }
}

Here is the second part:
package alpha;

public class AOuter
{
   private AInner aInner = new AInner();

   ////////////////////////////////////////////////////////////////////
   ///
   ///           SETTERS AND GETTERS FOR PROPERTIES:
   ///
   ////////////////////////////////////////////////////////////////////

   public           int getProperty1() { return aInner.property1; }
   protected        int getProperty2() { return aInner.property2; }
                    int getProperty3() { return aInner.property3; }
   private          int getProperty4() { return aInner.property4; }
   public static    int getProperty5() { return AInner.property5; }
   protected static int getProperty6() { return AInner.property6; }
   static           int getProperty7() { return AInner.property7; }
   private static   int getProperty8() { return AInner.property8; }

   public           void setProperty1(int i) { aInner.property1 = i; }
   protected        void setProperty2(int i) { aInner.property2 = i; }
                    void setProperty3(int i) { aInner.property3 = i; }
   private          void setProperty4(int i) { aInner.property4 = i; }
   public static    void setProperty5(int i) { AInner.property5 = i; }
   protected static void setProperty6(int i) { AInner.property6 = i; }
   static           void setProperty7(int i) { AInner.property7 = i; }
   private static   void setProperty8(int i) { AInner.property8 = i; }

   ////////////////////////////////////////////////////////////////////
   ///
   ///                         METHODS:
   ///
   ////////////////////////////////////////////////////////////////////

   public           void method1() { aInner.method1(); }
   protected        void method2() { aInner.method2(); }
                    void method3() { aInner.method3(); }
   private          void method4() { aInner.method4(); }
   public static    void method5() { AInner.method5(); }
   protected static void method6() { AInner.method6(); }
   static           void method7() { AInner.method7(); }
   private static   void method8() { AInner.method8(); }

   ////////////////////////////////////////////////////////////////////
   ///
   ///                       MAIN METHOD:
   ///
   ////////////////////////////////////////////////////////////////////

   public static void main(String args)
   {
      AOuter a = new AOuter();
      alpha.beta.B b = new alpha.beta.B();
      b.wibble(a.aInner);
   }
}


It is important in the above code that property AInner is private so that only the AOuter class can access it. The AInner property also has no getter method as having such would violate encapsulation and allow a back door into the class by allowing arbitrary outside access to the class AOuter's private methods and properties. For a similar reason the AInner property has no setter method. Here is the second file reworked to make it compile:

package alpha.beta;

public class B
{
   public void wibble(alpha.AInner aInner)
   {
      // Demonstration of successful attempt
      // to access every method and property
      // of a given class.

      aInner.property1++;
      aInner.property2++;
      aInner.property3++;
      aInner.property4++;

      // these properties are static
      // so they could also be prefixed
      // by alpha.AInner
      aInner.property5++;
      aInner.property6++;
      aInner.property7++;
      aInner.property8++;

      aInner.method1();
      aInner.method2();
      aInner.method3();
      aInner.method4();

      // these methods are static
      // so they could also be prefixed
      // by alpha.AInner
      aInner.method5();
      aInner.method6();
      aInner.method7();
      aInner.method8();
   }
}

The downside of this system is that only a method of the class AOuter can call B's wibble method. Therefore to call a method in a subpackage, you need to define and call a method in the class AOuter that in turn calls the method in the subpackage. You can think of the act of calling a method of a class X with a reference to the internals of a different class Y as a kind of friendship, as by passing a reference to the internals of Y you are granting access to private methods and properties of Y to methods in X which is outside of Y.

Note: Care must be taken to where the alpha.aInner reference is used, for example if it is stored somewhere else than a local variable of the wibble method, then this opens up access to the internals of the AOuter class to everywhere the alpha.aInner reference can be accessed.

4. An simpler but weaker version of the design pattern

Instead of splitting a class into an inner and outer components, a simpler but limited functionality version of the above-mentioned system can be achieved. Consider the following source file:

package alpha;

public class A
{
   // Defines a property with package visibility
   java.util.Vector vector = new java.util.Vector();

   // Provides the point of entry for an application
   public static void main(String args)
   {
      A a = new A();
      alpha.beta.B b = new alpha.beta.B();
      b.wibble(a.vector);
   }
}

In the above code, public access to the java.util.Vector internals of A has been granted by the call to the wibble method from the main method of the A class. Here is the file that contains the definition of the wibble method:

package alpha.beta;

public class B
{
   public void wibble(java.util.Vector vector)
   {
      System.out.println("B's wibble method");
      // Successful attempt to access a property
      // in a parent package
      vector.addElement("apple");
   }
}

Note that under this restricted system the Vector inside the A class cannot be re-bound to another Vector outside of the A class. It can only be altered by calling the public methods of the Vector class.

5. Further work

Extending the above-mentioned design pattern to methods that pass and return values of different types is left as an exercise for the reader. At the present time I haven't worked out how to apply this design pattern to private or package visibility methods in classes that inherit from and use properties of other classes.


If the developers of Java could be made aware of the limitation of the Java under the present system, then Java could be re-engineered so that it no longer has this limitation.


Adding support to the more general system of package visibility for fields would not change the behaviour of Java source files that presently compile. All it would do is make certain files that don't compile into files that do compile.


On the other hand if package visibility as applied to classes is to be reinterpreted, then some compiler trickery will be needed to ensure that files that compile under the old system give the same result as under the new system. As an example of a potential problem is for code that uses import statements to import a class from a from a parent package. Here is a file called Vector.java:

package alpha;

class Vector
{
}

Here is the second file, called Main.java:
package alpha.beta;

import java.util.*;
import alpha.*;

class Main
{
   public static void main(String args)
   {
      Vector v = new Vector(); // *** chooses java.util.Vector rather
                               // than alpha.Vector as alpha.Vector is
                               // not considered to be in the same package
      System.out.println(v);
   }
}

Because the Vector class has package visibility it is not accessible to the alpha.beta package. Therefore the compiler creates a new java.util.Vector at the line marked ***. Changing javac to get around this design flaw should not change the behaviour of classes that do compile under the current system. Therefore there needs to be a special case for classes defined with package visibility.


I hope that the cost of this burden in terms of additional compiler complexity is less than the value of the benefit obtained by the programmer by extending Java in this way.

Back to Research Projects
This page has the following hit count:
| Main Menu | Research Projects | Photo Album | Curriculum Vitae | The Greatest Artists |
| Email Address | Computer Games | Web Design | Java Training Wheels | The Fly (A Story) |
| Political Activism | Scruff the Cat | My Life Story | Smoking Cessation | Other Links |
| Debugging Macros | String Class I | Linked List System I | Java for C Programmers | Naming Convention |
| String Class II | How I use m4 | Strings III | Symmetrical I/O | Linked Lists II |
| Run-Time Type Info | Virtual Methods | An Array System | Science & Religion | Submodes |
| Nested Packages | Memory Leaks | Garbage Collection | Internet & Poverty | What is Knowledge? |
| Limits of Evolution | Emacs Additions | Function Plotter | Romantic Love | The Next Big Thing |
| Science Fiction | Faster Compilation | Theory of Morality | Elisp Scoping | Elisp Advice |
| S.O.G.M. Pattern | Safe Properties | School Bullying | Charisma Control | Life and Death |
| Splitting Java | Multiple Ctors | Religious Beliefs | Conversation 1 | Conversation 2 |
| J.T.W. Language | Emacs Additions II | Build Counter | Relation Plotter | Lisp++ Language |
| Memory Leaks II | Super Constructors | CRUD Implementation | Order a Website Form | There Is An Afterlife |
| More Occam's Razor | C to Java Translator | Theory of Morality II
Last modified: Sun Sep 25 16:11:47 NZDT 2016
Best viewed at 800x600 or above resolution.
© Copyright 1999-2016 Davin Pearson.
Please report any broken links to