30. Abuse of Class Hierarchy

Part of CS:2820 Object Oriented Software Development Notes, Spring 2021
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

Breaking up the Source File

The code for our road network simulator is getting large. At some point, it would make good sense to break it into multiple source files. The Java compiler allows you to say:

[HawkId ~]$ javac File1.java File2.java

This builds a project where the source is written in multiple files. However, if you had to type all the file names every time you compiled your program, it would get very tiring. Fortunately, the Unix/Linux shells (as well as the DOS/Windows command line) provide an abbreviation in the form of a wild-card character that matches essentially anything, so you can type:

[HawkId ~]$ javac *.java

This matches any file ending with .java, and it works well for a project where there is only one application in the directory.

The java compiler is fairly smart. If you type just this, the compiler will seek out other files in an intelligenty way:

[HawkId ~]$ javac RoadNetwork.java

Specifically, if the code in RoadNetwork.java refers to a class, for example, Simulation and that class is not defined in the current source file, the compiler will automatically look for a file named Simulation.java in the current directory and, if there is such a file, add it to the list of files it is compiling. It will not search outside the current directory.

If your project includes files that are in multiple directories, you can explicitly tell the compiler where they are using an @argfile. This tool allows you to have multiple main programs for a project, for example, main programs for testing classes in addition to the real main program, or code for shared components in other directories. An @argfile is a file with explicit lists of the source files to be used.

Suppose you wanted to call your argfile classes. You could construct such a file as follows:

Simulator.java
RoadNetwork.java

Given that this file exists, you can compile these two source files together with the command:

[HawkId ~]$ javac @classes

If the classes file gets big, it would be nice to be able to add comments to it. Unfortunately, Java does not have a standard way to put comments in @argfiles.

Breaking Out One Class

In the above example, we assumed that the classes file specified a separate source file for class Simulator. We can break this out of the original monolithic source file like this:

// Simulator.java

import java.util.PriorityQueue;

/**
 * Framework for discrete event simulation.
 * @author Douglas Jones
 * @version ...
 */
class Simulator {

    public interface Action {
	// actions contain the specific code of each event
	void trigger( float time );
    }

	... remainder of body of class ...
}

Note that our original source file had imported a large number of different classes. Of those, only class PriorityQueue is relevant to class Simulator, so that is the only import statement used in this source file.

Note also that the header comments from class Simulator have been expanded to include authorship and other information. When a source file is broken into multiple files, the relationship between those files needs documentation, and the Javadoc comment mechanism is reasonably good for this. Each source file is worthy of comments indicating authorship, version and the provenance of this file.

Class Simulator is almost the perfect size for a single class that fits comfortably in one source file. Some of our other classes are rather small, while others are a bit large.

The Remainder

Jave works nicely if all the classes you use are defined in one source file, and it works nicely if each class is in its own source file, so long as the source file has the same name as the class. Breaking up programs along other lines is more difficult. The problem is that if a class is needed, and the definition of that class is in a file with a different name, the compiler will not be able to find the file. It is only smart enough to look for a file with the same name as the class.

As a result, the "right" way to break up a large project into multiple source files puts one class in each file.

As a general rule, if the single-source file had a number of import statements at its head, when you break the program into multiple source files, each of those files is likely to have a much smaller number of import statements. You should never import files you don't need, since the import statements themselves serve as useful documentation of the relationship between the program and the tools in the Java library.

Access Rights

When a program is broken into multiple source files, there must be exactly one public class in each source file. That is the only class that can be directly accessed from the outside world. Other classes in the same source file can be declared with default access, which makes them accessible to all of the other classes in the same source file but not outside that file.

Only one public class in the program can have a public method called main. The whole idea of a public main method is something Java inherited from C++ and ultimately C.

Up to this point, we have not really distinguished between Java's default access rights, what you get when you say int i and public access, as in public int i.

This is because, so long as all the classes in a program are in the same source file, fields defined with default access are identical to fields with public access. When the file is broken into multiple files, however, things change. Fields declared as public are visible from code compiled in other source files, while fields with default access are only visible in the same source file where they are declared.

This relates strongly to features of C++ and C where source files were a primary access control mechanism. With inner classes, Java could have solved these kinds of problems quite differently, but inner classes are an afterthought in the history of Java, and to this day, access rights

README files

Any time a large project reaches the point where there are too many files and no roadmap, we need to do something. In the UNIX world, the first solution to emerge was the README file. By convention, this name is always given in upper-case to make it stand out.

Here is a rudimentary README file:

Road Network Simulator
Version: Nov 11, 2020 -- new simulation framework based on the Nov 3 version
Author: Douglas Jones

This distribution contains
-- README -- this file
-- RoadFiles -- the list of all Java source files
-- testfile -- a road network description to test the simulator

To build the simulator use the shell command: javac @RoadFiles
To generate HTML documentation, run the shell command: javadoc @RoadFiles

To test the simulator use the shell command: java RoadNetwork testfile

Other input files can be substituted for testfile.
    BUG:  document the input file format somewhere
Unfortunately, there is no single standard for the structure or contents of a README file, but there are a growing number of best-practices documents surrounding such files:

Red Hat's tutorial suggests the following sections

Some software distributions split these apart, so you might have a file called COPYRIGHT or LICENSE referenced from the main README file, and a file called AUTHORS constructed similarly.

Just to add to the confusion, there are several markdown languages that people use for README files. A markdown language is a grossly simplified markup language, designed to be trivial to edit using a plain-text editor such as vi or emacs. One of the common markdown languages commonly used on GitHub and with the .md file extension can be summarized by this brief bit of text:

# Title

Text between titles to
be set as a paragraph

Blank lines separate paragraphs

## Subtitle

* Bulleted list
* Another list element

### Sub-Subtitle

A paragraph of text introducing
a block of pre-formatted text

```
quoted source code
or shell commands
```

Just to keep people confused, a competing common markdown format uses this notation:

HEADING
=======

a paragraph of
text

Sub-heading
-----------

If you need _italic_ or **bold** or `monospace`
they are available

- Bulleted lists work
- And you can include [link text](http://url.of.link)

> block quotes marked
> like this bit of text

All of these markdown languages are intended to be easy to read in a plain text file without needing a manual to explain the markdown language. Each of these markdown languages has evolved to the point that they threaten to be as complex as HTML and violate their original intent. The general advice is to avoid this complexity. Keep things simple!

Many software tools now recognize the file extension .md, and if you try to use a markdown language for your README file, you may as well name it README.md and see how your desktop software responds when you click on it. Many of the common markdown file rendering programs try to accept any or all of the notations mentioned above.

So, as an exercise, let's try to write a better README for the road network simulator:

# Road Network Simulator

A discrte-event simulator for a road network, built in Java.  The
network consists of any number of intersections connected by roads.
Intersections may be guarded by stop lights or uncontrolled.  A
special intersection type injects vehicles into the model and
another consumes vehicles.  Roads and intersection have each have
the time it take a vehicle to traverse them.

This is really a feasibility demonstration, because the only output
it produces is a trace of activity.  Furthermore, vehicles have no
identity and no agenda but just bounce around the road network at
random.  There is no congestion penalty for roads.

## Install

This file contains a file called `RoadFiles` listing all the Java
source files.  Each source file contains one top-level class.
RoadFiles is divided into 3 sections:

* Utility classes including the simulation framework.
* The simulation model, roads, intersections and types of intersection.
* The main program to read the model and run the simulation.

### Prerequisites

The code is in Java 8, it uses some lambda expressions.

The following instructions assume use of some variant of the Unix
shell, as is commonly used on Linux and MacOS for command line input.

### Instruction

To compile the entire project, use this shell command:

```
	javac @RoadFiles
```

To build the HTML documentation for the internals of the code, use
this shell command:

```
	javadoc @RoadFiles
```

### Cleanup

The above steps created a large number of `.class` files containing
parts of the compiler output, and a large number of `.html` files
containing documentation, plus `package-list` and some `.css` and
`.js` files.  These clutter up the directory and, since all are
automatically generated, you can delete them with the following shell
command when they get in the way:

```
	rm -f *.class *.html package-list *.css *.js
```

## Usage

The distribution contains a file called `testfile` that describes a
trivial little road network.  After installing the simulator, you
can test and demonstrate it shell commands such as the following:

```
	java RoadNetwork testfile
	java RoadNetwork testfile 10.0
```

You may substitute your own road network description for `testfile`
and as the second example above demonstrates, you may specify a time
limit for the simulation shorter than the limit given in the test
file.  If none is given, the simulation will run forever.

BUG Unfortunately, we not yet written a decent description of the test
file format, so you'll have to infer that from the example.  Mea culpa!

## Contributing

Report bugs to the first author, although this project is not currently
under active development.

## Authors

- Douglas Jones, Department of Computer Science, University of Iowa

## License

Free software, the author makes no claim on it and doesn't want to be
bothered by people asking to use it.  Just take it, it's yours!

## Warranty

You get what you paid for.  If this does anything for you, great, but
don't bother me if it doesn't work.

## Version History

2020-11-12 -- new anti-lambda expression simulation framework

## Acknowledgements

Thanks to all the students who've suffered through the lectures during
which this simulation program was used as a running example.

Note that the Lisencing section above, with a home-brew lisence notice, is not a good idea. Writing a good software lisence takes work and some understanding of copyright law. If you intend the give away the software, you might considere consulting the Gnu General Public License, or GPL (see the Wikipedia article), or alternatiely, one of the Creative Commons licenses (see the Wikipedia article).These licenses have been gone over by good lawyers and crafted with far more care than you are likely to be able to devote to this subject.

Note that under current US law, everything you write is inherently subject to copyright, so if you do not explicitly give your rights away, they are yours, except if someone paid you to do the work. In that case, your employer owns the work. This us usually documented in an employment contract where you explicitly convey copyright for all work done on your employer's time to your employer.

A common result of the automatic copyright you own to everything you do is that the work becomes an orphan work. That is, with no copyright notice and no way to trace the author, others who find the work can make no use of it. A huge fraction of all software that has been written is now orphaned, with no legal way to make any use of it because the owner of the copyright cannot be traced.

Also note that the waranty disclaimer above is problematic. Because the software is given away free, I can release it with a warning and take no responsibility, but the moment I sell something, it falls under a common law warranty: The warranty of implied merchantability, fitness for purpose, and workmanlike quality (see Wikipedia).

If you sell something as simple as a shovel, even though you never mentioned any warranty, the mere act of selling the shovel and describing it as a shovel implies a warranty that it conforms to the usual expectations of what shovels do. Fitness for purpose indicates, in the case of a shovel, that it will actually dig, and workmanlike quality implies that the shovel was built to the usual standards of shovel making.

It is legal to sell something with a warranty disclaimer, for example, when you sell a used shovel "as is." At this point, the buyer assumes all liability for that shovel. Products sold "as is" usually come with a steep discount.

In the world of software, it is very common to sell with a strict disclaimer of all implied liabilities. Typically, these were printed on the shrink wrap covering the floppy disks that came from companies like Apple and Microsoft, and a typical term in the disclaimer said that, by breaking the shrink wrap, the buyer agrees to the terms of the disclaimer. These were called shrink-wrap lisences. Nowdays the act of clicking on the install button on a software distribution sometimes includes an agreement to similar disclaimers, and these click-through disclaimers are sometimes still called shrink-wrap license agreements.

The legality of such disclaimers is open to debate. I suspect that many programmers, in the future, will have to deal with offering real warranty terms on their code.