Ballworld Provided Utilities

COMP 310    Java Resources  Eclipse Resources

(Go to Ballworld Demos)

A number of utilities are being provided for use in the Ballworld-related assignments (and other assignments if desired, though they may be supplanted by other utilties).

In GitHub Classroom, these packages are supplied as a submodules in the starter code.


Quick links:

provided Package Contents:

provided.util package:

provided.utils.dispatcher package:

This package provide interface definitions used to implement the Observer-Observable Design Pattern.

IDispatcher<TMsg> -- An Observable entity that will send update all its Observers by sending TMsg-type message to them.

IObserver<TMsg> -- An Observer that is updated by an Observable (an IDispatcher) that sends it TMsg-type messages.

provided.utils.loader package:

This package provides some general purpose dynamic class loading utilities that can be used to load any type of class.

IObjectLoader<ReturnT> -- A general purpose dynamic class loader utility that can load any subtype of ReturnT with a set of contructor parameters that matches a constructor of that given subtype.

provided.utils.valueGenerator package:

This package provides utilities for generating random and other values.

IRandomizer -- A set of generating methods for random numbers, points, dimensions, colors, etc.

ISineMaker -- A utility for generating a sequence of sinusoidally varying numerical values.

IVectorUtil -- A utility for common vector/point operations. Works on Point2D objects, which includes both Point and Point2D.Double objects.

IAverager<TInput, TOutput> -- A generic represention of an object that can perform weighted averages of input values.

provided.utils.displayModel package:

This package provides utilities and definitions useful for representing common displayable elements in the model in a view-independent manner.

THIS PACKAGE IS TO BE USED ON THE MODEL SIDE ONLY! Do NOT couple the model and view by letting both sides know about these representations! Let the controller build adapters that will utilize components from the view to construct displayModel elements that the model can use without knowing anything about the view.

IDimension -- A representation of an entity that has integer width and height values. Use this interface instead of exposing the model to view components, e.g. the JPanel upon which the balls are bouncing, or from having to make calls on the model-to-view adapter every time that the width and height of the ball canvas is needed. By using an IDimension instance, the querying of a view component is hidden and the model only needs to manage a simple object that supplies a dynamic width and height on demand. Note that implementations of IDimension enable's the system to retrieve the dynamic width and height of a view's component, that is, the current width and height of a resizable component. This is opposed to java.awt.Dimension, which is only capable of returning a static width and height captured at a particular moment in time.

IATImage -- A representation of an affine transformable image. This interface is designed to wrap a java.awt.Image object and its attendent view-supplied java.awt.image.ImageObserver and java.awt.MediaTracker objects.

In the Java graphics system, Image objects need a MediaTracker object to help insure that operations wait until the image is fully loaded from the disk. Also, Image objects need an ImageObserver object whenever the width or height is read and whenever the image is displayed. The ImageObserver object helps translate the image's pixels onto the pixels of the screen. Since the required ImageObserver is the component upon which the balls are bouncing and the MediaTracker needs that same component to be instantiated, utilizing these objects in the model effectively couples the model to the view. But by encapsulating everything inside of an IATImage instance, the model can be decoupled from the view by only interacting with a view-independent representation. Plus, by hiding away some of the image manipulation nitty-gritty, it is easier to work with an IATImage instance than working directly with an Image instance.

In general, an IATImage instance would be generated via a model's call to a model-to-view adapter. That adapter's code, typically in the controller if anonymous inner classes are used, would take an Image object from the model and a component from the view (the ball's canvas component) and return an IATImage instance to the model.

Important: Wrapping an Image in an IATImage does NOT change either its dimensions or its location i.e. it is still located with (0,0) at it upper-left corner.

IATImage.FACTORY is a static BiFunction<Image, Component, IATImage> lambda that takes an Image and a Component and returns an IATImage instance that holds (wraps) the two input values in its closure. This factory method is supplied as a convenience.

IMPORTANT: Image objects should be loaded from the disk in a system-independent manner using Java's built-in resource loading capabilities. Do NOT use normal file-loading methods! This resource-loading technique will work on PC's, Mac's, Linux and even inside of JAR files:
Image image = java.awt.Toolkit.getDefaultToolkit().getImage(this.getClass().getResource(filename));

For full decoupling of the model, the java.awt.Toolkit.getDefaultToolkit().getImage(url) part of code could be placed in the adapter code in the controller. Don't move the this.getClass().getResource(filename) part into the controller as that would affect the relative pathname required for the image file.

provided.utils.view package:

This package contains GUI components that are useful for displaying dynamically generated components in the view.

IMPORTANT: This package is deliberately restricted to dynamically generated javax.swing.JComponents (NOT java.awt.Component!) so that it can access the more advanced features of the Swing GUI components. It is HIGHLY RECOMMENDED to use ONLY Swing components in your GUI's!

TabbedPanel: This JPanel derivative will display a dynamically generated component on a tabbed display. The tab's label is set when the component is generated so that similar components can be easily differentiated. The dynamically generated component are always surrounded by automatic scroll bars to ensure that the entirety of the component is always accessible. A command that can be used to remove the new tab with its contained component is also automatically generated. A TabbedPanel can be used anywhere a JPanel can be used in one's GUI. A title can be supplied to the panel that will be displayed as a titled border to help label the component in a larger GUI. An empty string title can be used if not titled border is desired. An optional ILogger can be supplied as well if logging to something other than the shared system logger is desired.

public Runnable addComponentFac(String label, Supplier<JComponent> fac, [boolean addScroll]): This method uses the supplied JComponent factory (Supplier<JComponent>) to instantiate a new JComponent to be displayed on a new tab.

  • The supplied label is applied as the tab's label to identify the new component to the user.
  • A Runnable command is returned that when run, will remove the tab generated by this method.
  • This method can be invoked from any thread and the given factory is guaranteed to be properly run on the GUI event thread.
  • The optional addScroll parameter controls whether or not the supplied component is installed inside of a JScrollPane (defaults to result of getDefaultAddScroll()).

WARNING: The given factory MUST INSTANTIATE the resultant component when its get() method is executed! It shoild NOT simply return an already instantiated GUI object! The Java standard REQUIRES that ALL GUI components be instantiated on the GUI event thread and TabbedPanel will guarantee that the factory's get() method is run on that thread.

public void setDefaultAddScroll(boolean addScrollDefault)
public boolean getDefaultAddScroll()

Accessors for the default value of whether or not components are added with a JScrollPane by default. Defaults to true.

public JTabbedPane getTabbedPane(): NOT TYPCIALLY USED!! Accessor for internal tabbed pane for use in highly customized scenarios only.


Simple Example:

// Create a new tab labeld "My New Component", displaying an instance of MyCustomComponent	
Runnable cmd = aTabbedPanel.addComponentFac("My New Component", ()-> {
	return new MyCustomComponent();  // return value of Supplier.get() method

// Remove the tab created above.;

Example with creating a mini-MVC:

This is just one of many, many ways to accomplish this! The key issues here are to

  1. Ensure that the mini-View (the entire mini-controller here) is instantiated and started on the GUI thread and
  2. To enable the mini-controller to access the tab-closing command that will close the tab that holds the mini-View.

Note that references the instantiated mini-Controllers below may need to be stored in the main-Controller for use in other operations, e.g. stored as a Set<MiniController> or as a Map<UUID, MiniController> if a unique ID for the mini-Controller is available, such as a channel ID value in publish-subscribe systems.

 * A optional field in the main MVC code where IRoomNameIDDyad holds a room name and a channel ID and is obtained via a method on 
 * the mini-controller.  The dyad is assumed to properly implement .equals() and .hashCode() (by delegating to the internal UUID)
 * This mapping enables the system to determine if it already has a particular room and to access the mini-controller for a specific room 
 * if needed, e.g. to loop through all the rooms to get them to exit when quitting the main application.
private Map<IRoomNameIDDyad,  MiniController> roomMap = new HashMap<>();

// -----------------------
// In main MVC controller constructor.  The main View holds a TabbedPanel and simply mirrors out its addComponentFac() method.

mainView = new MainView(new IMainView2MainModelAdapter() {
	// A method to create a new mini-MVC with a given name
	public void makeMiniMVC(String name) {
		// Rather than invoking the following code directly, it is very common to encapsulate the process below into a method 
		// that takes both a name and a room ID (channel ID) where if the ID is null, a new room is made, otherwise an existing 
		// channel is joined.   There are many ways to accomplish these tasks; please don't take anything as gospel!

		Runnable[] closeTabCmd = new Runnable[]{null}; // One-element array trick enables access below.
		// The mainView.addComponentFac() just delegates to its internal TabbedPanel instance.
		closeTabCmd[0] = mainView.addComponentFac(name, ()->{ // Lambda expression for Supplier.  The code is for the Supplier.get() method
			// By instantiating the entire mini-controller inside of the Suppler.get() method, 
			// we ensure that the mini-View is instantiated and started on the GUI thread!
			MiniController miniController = new MiniController(name, new IMiniMVC2MainMVCAdapter() {
				// If one-element array trick is used for miniController variable, then exit() does not need a parameter.
 				 * The mini-controller telling the main controller to complete the room exit process.
				 * Called by the mini-MVC when it has terminated and wants to have its view removed from the main MVC.
				 * @param thisMiniController The mini-controller calls this method passing a reference to itself.  
				 *		Parameter is not needed if using one-element array trick and can be omitted from the definition of the method.  
				 *		Shown this way just for example purposes.
				public void exit(MiniController thisMiniController) { 
					closeTabCmd[0].run(); // Close the tab when the miniMVC wants to exit
					// If the instantiated mini-controllers are being stored in the main controller, be sure to remove this mini-controller!
					// May need to use one-element array trick instead of simple local variable above or pass the reference to the mini-Controller as 
					// an input parameter to this exit() method in order (as shown above) to access the mini-Controller instance at this point.
					roomMap.remove(thisMiniController.getNameIDDyad());  // For example

			JComponent miniView = miniController.start(); // The start() method returns the started mini-View to be placed into a tab in the main View.
			// If the mini-Controller instances are being stored by the main controller, e.g. in a Set or {UUID: MiniController} 
			// or {name_ID_dyad:  MiniController} dictionary, then save it here. Note that the miniMVC is guaranteed to have a valid ID at this point.
			roomMap.put(miniController.getNameIDDyad(), miniController);   // For example
			return miniView; // For the main view to display


TabbedFrame: This JFrame derivative is a convenience class that wraps a TabbedPanel and displays it in its own free-standing frame. This frame is useful when the GUI does not have enough room to properly display a TabbedPanel and a multi-frame user interface is an acceptable user interface design.

OPERATIONAL NOTE: Since it is generally not desireable to close all the control panels and also not desireable to exit the application when closing a control panel frame, TabbedFrame is configured such that it CANNOT CLOSE. If this behavior is not desired, it can be overridden using the same techniques used to control the closing behavior of JFrames.

Usage: Once instantiated, a TabbedFrame is used in exactly the same manner as a TabbedPanel to display dynamically generated components.


ValuesPanel: This JPanel derivative is capable of displaying multiple sub-components for user input of various data types ranging from Strings, integers, doubles, booelans, choices of objects and fully custom inputs, including sub-ValuesPanels. Any number of inputs of any type can be created and displayed at once. The typical usage for this component is as a new component added to a TabbedPanel or equivalently, to a TabbedFrame. A long text description of the usages of all the displayed is supplied to the panel's constructor to be displayed to the user. A number of convenience methods are provided to enable easy creation of common input types. A ValuesPanel can be used anywhere a JPanel can be used.


Example code

	    // The controller connects the fac2ModelAdpt.addConfigComponent() method to a TabbedPanel or TabbedFrame's addComponentFac() method.
		// Add the component made by the given factory to the tabbed display under a tab with the given tabName.
		fac2ModelAdpt.addConfigComponent(tabName, ()->{

			// This factory instantiates a ValuesPanel to be added to the tabbed display
			ValuesPanel pnlValues =  new ValuesPanel("""
A description of the various inputs on this ValuesPanel and their usages.
This description appears at the top of the ValuesPanel.
			""", logger);

			// Add a double value input to the ValuesPanel
			pnlValues.addDoubleInput("Double Value Input", currentDblVal, (newVal)->{

				// Validate the input before setting it.
				if(0 < newVal) {  // Here, newVal must be non-negative
					currentDblVal = newVal;  // Is ok.  Accepts the new value
				else {
					// Negative value.  Rejects the new value.  Current value is unchanged.
					logger.log(LogLevel.ERROR,"Double Value Input must be greater than zero!");
				return currentDblVal;  // Return the current value to be displayed.
			// Add a boolean value input
			pnlValues.addBooleanInput("Status","Enabled", isEnabled, (newVal)->{
				isEnabled = newVal;  // No input validation being done here in this example.
				return isEnabled; // Return the current value to be displayed.
			// Add a droplist of choices input to the ValuesPanel
			pnlValues.addDropListInput("Choices Input", choice1, (IChoiceObj newVal)->{
				currentChoice = newVal;   // No input validation being done here in this example.
				return currentChoice;  // Return the current value to be displayed.
			}, choice1, choice2, choice3);
			return pnlValues;   // return the ValuesPanel to be put on the tabbed display


Example Screen Shots

TabbedFrame (with embedded TabbedPanel) with multiple ValuePanels: Boolean input


TabbedFrame (with embedded TabbedPanel) with multiple ValuePanels: Multiple double inputs and a choice input



The Javadocs in the actual provided code will be more up-to-date than that which could be presented here, so please use that as the "official" documentation.


provided Package Javadoc

© 2020 by Stephen Wong