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.
NOT ALL SUB-PACKAGES ARE DISCUSSED HERE. PLEASE SEE THE JAVADOCS IN THE CODE FOR MORE INFORMATION.
provided.utils.
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 sendingTMsg
-type message to them.
- Implemented by the provided classes:
provided.utils.dispatcher.impl.SequentialDispatcher
-- Sends updates to theIObservers
sequentially.provided.utils.dispatcher.impl.ParallelDispatcher
-- Sends updates to theIObservers
in parallel, if possible..- Typical usage:
IDispatcher<Graphics> myDispatcher = new SequentialDispatcher<Graphics>(); // Sends Graphics objects as messages to the observers.
IObserver<TMsg>
-- An Observer that is updated by an Observable (anIDispatcher
) that sends itTMsg
-type messages.
- Typical usage:
class MyBall implements IObserver<Graphics> {...} // A ball that receives Graphics object messages when updated by the dispatcher.
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 ofReturnT
with a set of contructor parameters that matches a constructor of that given subtype.
- Implemented by the provided classes:
provided.utils.loader.impl.ObjectLoader
-- A concrete implementation of theIObjectLoader
interface that requires the fully qualified classname of the object to instantiate..provided.utils.loader.impl.ObjectLoaderPath
-- A concrete implementation of theIObjectLoader
interface that searches through a list of supplied packages for the given classname.
- Only the classname, without any package specifications are needed.
- The given package names are each pre-pended to the given classname to see if the class exists in in that package.
- The given package names are specified using a period to separate super- and sub-packages. The name of a package must end in a period, ".".
- Error factory:
- The error factory is a functional (
java.util.function.BiFunction
) that given to the loader's constructor is a function that returns a valid object of typeReturnT
. In general, this would be some sort of "error" object that behaves in a manner that clearly indicates that an error has occurred, e.g. logs error messages whenever it is used. The factory takes the same parameters as is given toILoader.loadInstance()
, i.e. a fully qualified pathname of the attempted classname (the last attempt forObjectLoaderPath
) and the input arguments for constructing the loaded object.- Typical usage:
IObjectLoader<IObserver<Graphics>> loader = new ObjectLoader<IObserver<Graphics>>( (attemptedClassname, args) -> new ErrorBall(attemptedClassname, viewAdpt.getCanvasDim(), 100)); // Creates a loader that will instantiate IObserver<Graphics> objects and if there is a problem, will instantiate an ErrorBall object.
IObserver<Graphics> ball = loader.loadInstance(aBallClassName, aLocation, aRadius, aVelocity, aColor, aPanel); // instantiates a ball of fully qualified classname, aBallClassName, with the given constructor parameters.
- Question: Why is the typing of the left side above for
IObserver
and notABall
orBall
?
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.
- Implemented by:
provided.utils.valueGenerator.impl.Randomizer
-- Concrete implementation ofIRandomizer
.- Typical usage:
IRandomizer rand = Randomizer.Singleton; // Uses the singelton instance of the that concrete implementation.
ISineMaker
-- A utility for generating a sequence of sinusoidally varying numerical values.
- Implemented by:
provided.utils.valueGenerator.impl.SineMaker
-- Concrete implementation ofISineMaker
.- Typical usage:
ISineMaker sineMaker = new SineMaker(minRadius, maxRadius); // Creates an ISineMaker that will produce a series of values varying from minRadius to maxRadius.
aBall.setRadius(sineMaker.getIntVal()); // Sets the ball's radius to the next value from the ISineMaker.
IVectorUtil
-- A utility for common vector/point operations. Works onPoint2D
objects, which includes bothPoint
andPoint2D.Double
objects.
- Available utility methods:
void rotate(Point2D v, double angle)
: Mutate the given vector by rotating it by the given angle (in radians)double angleBetween(Point2D v1, Point2D v2)
: Calculate the angle (in radians) from one vector to another.Point2D vectorTo(Point2D source, Point2D target)
: Find the vector difference oftarget - source
, i.e. the result is the vector that points fromsource
totarget
.- Implemented by:
provided.utils.valueGenerator.impl.VectorUtil.Singleton
-- Concrete implementation ofIVectorUtil
.
IAverager<TInput, TOutput>
-- A generic represention of an object that can perform weighted averages of input values.
- Generic types:
TInput
: The type of value being averaged.TOutput
: The type resultant average- Available methods:
void addTo(double weight, TInput value)
: Add the given value and its associated weight to the running average.
- The weight values must be non-negative (throws an
IllegalArgumentException
)doubles
but do NOT need to be normalized. The averager will automatically normalize the weights.boolean hasValidAvg()
: Returnstrue
if a valid average has been accumulated so far.
- This method is used to prevent obtaining the average before any values have been accumulated.
TOutput getAvg()
: Get the current average value.
- Throws an
IllegalStateException
ifhasValidAvg()
returnsfalse
.void reset()
: Resets the averager, including all accumulated values and weights.- Implemented by:
provided.utils.valueGenerator.impl.ScalarAverager
-- An averager that can averageints
,doubles
andfloats
and any combination of those types of values.
- The average value is a
double
.provided.utils.valueGenerator.impl.VectorAverager
-- An averager that can average any combination ofPoint2D
objects (e.g.Point
andPoint2D.Double
).
- The average value is a
Point2D.Double
.
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
elements that the model can use without knowing anything about the view.
displayModel
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 anIDimension
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 ofIDimension
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 tojava.awt.Dimension
, which is only capable of returning a static width and height captured at a particular moment in time.
- Implemented by:
- Varies by system but typically by an adapter from the model to the view, i.e. in that adapter's code in the controller when using anonymous inner classes for the adapters.
- Typical usage:
- In model:
IDimension ballCanvasDim = model2ViewAdapter.getCanvasDim(); // ballCanvasDim is used by the balls to determine their bouncing behavior.
- In the controller, in the
IModel2ViewAdapter
code (the interface name may differ, especially if there are multiple adapters from the model to the view)
/** * Get an IDimension object that reflects the current dimensions of the ball's canvas component in the view. * @return an IDimension instance */ public IDimension getCanvasDim() { return new IDimension() { public int getWidth() { return view.getCanvas().getWidth(); } public int getHeight() { return view.getCanvas().getHeight(); } }; }
IATImage
-- A representation of an affine transformable image. This interface is designed to wrap ajava.awt.Image
object and its attendent view-suppliedjava.awt.image.ImageObserver
andjava.awt.MediaTracker
objects.
In the Java graphics system,
Image
objects need aMediaTracker
object to help insure that operations wait until the image is fully loaded from the disk. Also,Image
objects need anImageObserver
object whenever the width or height is read and whenever the image is displayed. TheImageObserver
object helps translate the image's pixels onto the pixels of the screen. Since the requiredImageObserver
is the component upon which the balls are bouncing and theMediaTracker
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 anIATImage
instance than working directly with anImage
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 anImage
object from the model and a component from the view (the ball's canvas component) and return anIATImage
instance to the model.Important: Wrapping an
Image
in anIATImage
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 astatic BiFunction<Image, Component, IATImage>
lambda that takes anImage
and aComponent
and returns anIATImage
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 thethis.getClass().getResource(filename)
part into the controller as that would affect the relative pathname required for the image file.
- Implemented by:
- Varies by system but typically by an adapter from the model to the view, i.e. in that adapter's code in the controller when using anonymous inner classes for the adapters.
- Typical usage:
- In the model:
- Wrap an
Image
object:IATImage atImage = model2ViewAdapter.getIATImage(image); // image is an Image instance
- Get the width or height of the image:
atImage.getWidth()
,atImage.getHeight()
- Draw the image:
atImage.draw(g, netAT); // netAT = the net affine transform that takes a image with (0,0) at its upper left corner and non-unit size and transforms it to the proper size and location on the screen.
- In the controller, in the
IModel2ViewAdapter
code (the interface name may differ, especially if there are multiple adapters from the model to the view)
/** * Return an IATImage that wraps the given Image object * and the ball's canvas from the view. * @param image The Image object to imbed in the IATImage instance * @return An IATImage instance */ public IATImage getIATImage(Image image) { return IATImage.FACTORY.apply(image, view.getCanvas()); }- Alternative usage that gives an
IATImage
object factory to the model rather than just theIATImage
objects themselves. This frees the model from having to make multiple calls to the view every time it wants to wrap anImage
in anIATImage
.
- In the model:
- Get an
IATImage
factory:Function<Image, IATImage> atImageFac = model2ViewAdapter.getIATImageFac(); // atImageFac is a function that takes a Image and returns an IATImage instance with that Image in its closure.
- Wrap an
Image
object:IATImage atImage = atImageFac.apply(image);
- In the controller, in the
IModel2ViewAdapter
code (the interface name may differ, especially if there are multiple adapters from the model to the view)
/** * Return an IATImage factory that can wrap a given Image object * and the ball's canvas from the view into the closure of a generated IATImage instance. * @return A Function<Image, IATImage> instance */ public Function<Image, IATImage> getIATImageFac() { // Curry the view's ball canvas (an ImageObserver) into a new IATImage-producing factory. return(image) -> { return IATImage.FACTORY.apply(image, view.getCanvas()); }; }
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
(NOTjava.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
: ThisJPanel
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. ATabbedPanel
can be used anywhere aJPanel
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 optionalILogger
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 suppliedJComponent
factory (Supplier<JComponent>
) to instantiate a newJComponent
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 aJScrollPane
(defaults to result ofgetDefaultAddScroll()
).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 andTabbedPanel
will guarantee that the factory'sget()
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 totrue
.
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. cmd.run();Example with creating a mini-MVC:
This is just one of many, many ways to accomplish this! The key issues here are to
- Ensure that the mini-View (the entire mini-controller here) is instantiated and started on the GUI thread and
- 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 aMap<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
: ThisJFrame
derivative is a convenience class that wraps aTabbedPanel
and displays it in its own free-standing frame. This frame is useful when the GUI does not have enough room to properly display aTabbedPanel
and a multi-frame user interface is an acceptable user interface design.
- A title is supplied to the constructor that is the displayed title of frame.
- An optional
ILogger
is supplied to the constructor to be used by the component. Defaults to the shared system logger.public void start()
: Starts the frame and makes it visible.- Mirrors of all
TabbelPanel
methods which enable access to and control of internalTabbelPanel
. See above.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 ofJFrames
.Usage: Once instantiated, a
TabbedFrame
is used in exactly the same manner as aTabbedPanel
to display dynamically generated components.
ValuesPanel
: ThisJPanel
derivative is capable of displaying multiple sub-components for user input of various data types ranging fromStrings
, 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 aTabbedPanel
or equivalently, to aTabbedFrame
. 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. AValuesPanel
can be used anywhere aJPanel
can be used.
- A
String
description of all the inputs and an optionalILogger
are supplied to the ValuesPanel constructor.public void addInputComponent(String title, Supplier<JComponent> compFac)
: This general purpose method uses the suppliedJComponent
factory to add a new component to theValuesPanel
display. See the important usage notes on the factory in the above description ofTabbedPanel
. The factory is guaranteed to be run from the GUI event thread. The given title is used for the titled border surrounding the newly instantiated component. This method can be safely called from any thread.- Convenience methods: These methods are just shortcuts to the
addInputComponent()
method for specific input types.
- Common features:
title
: The title used on the titled border surrounding the new input component.initValue
: The intially displayed value for the input.newValFunc
: A function that takes the newly input value and returns the new value being used. This enables validity checks to be made on the input value as input by the user, e.g. if the value needs to be non-negative, and to reject the new value if ncessary. The returned value is the value the component will use as its current value, independent of what the user submitted.- User typed values: These components accept inputs where the user types in the new value.
- Common features:
- Displays the current value
- Has a text field to input the new value
Has anEnter
button to submit the new value
- If possible, valid conversion to the desired data type from the typed in string is checked.
public void addTextInput(String title, String initValue, Function<String, String> newValFunc)
: Inputs aString
value.public void addDoubleInput(String title, double initValue, Function<Double, Double> newValFunc)
: Inputs a double precision floating point number valuepublic void addIntegerInput(String title, int initValue, Function<Integer, Integer> newValFunc)
: Inputs an integer number value.- User selected values: These components accept inputs that have been selected with the mouse.
- Common features:
- No textfield input
- Some components submit the new value as soon as it is selected, others may have an
Enter
button to submit the selected value.public void addBooleanInput(String title, String label, boolean initValue, Function<Boolean, Boolean> newValFunc)
: Inputs a boolean value. The displayed checkbox always shows the current value (true = checked). The new value is submitted as soon as the checkbox changes. The supplied label is for the displayed checkbox.public <TDropListItem> void addDropListInput(String title, TDropListItem initValue, Function<TDropListItem, TDropListItem> newValFunc, TDropListItem... items)
: Selects an input object from a drop list (JComboBox
) of choices. The type of object,TDropListItem
, is inferred from the input parameters. Theitems
vararg input parameter is a listing of all the possible choices to display. Be sure that theinitValue
object is in theitems
list!public void addIntSliderInput(String title, int initValue, Function<Integer, Integer> newValFunc, int minVal, int maxVal)
: Adjust an integer value by moving a pointer on a slider bar. New values are submitted as soon as the pointer is moved.public void addDblSliderInput(String title, double initValue, Function<Double, Double> newValFunc, double minVal, double maxVal, double step)
: Adjust a double precision value by moving a pointer on a slider bar. New values are submitted as soon as the pointer is moved. A minimum step size between submited values is specified. Note that round-off errors may affect the displayed and submitted values.
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 embeddedTabbedPanel
) with multipleValuePanels
: Boolean input
TabbedFrame
(with embeddedTabbedPanel
) with multipleValuePanels
: 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