Cartesian Plane Lesson 18 Page 3: Profiles

Profile Class, ProfileUtilsclass, JUnit

On this page, we will formulate the Profile class. This class will encapsulate the following data:

  • A Profile name
  • The grid-unit
  • The properties managed by GraphPropertySetMW and
  • All properties managed by the LinePropertySets for the axes, grid lines, and major and minor tics.

In this regard, a Profile will be closely associated with the PropertyManager. By default, the PropertyManager initializes the Profile upon instantiation, and a Profile can be used to update the current values of the properties under the control of the PropertyManager. A Profile can be saved to a file, which can be used to configure the main window layout at the operator’s convenience.

We’ll begin by updating CPConstants with declarations to encapsulate the names and values of properties stored in a Profile.

GitHub repository: Cartesian Plane Lesson 18

Previous lesson: Cartesian Plane Lesson 18 Page 2: Refactoring

Refactoring: CPConstants

To support the Profile class, we have to add to the CPConstants class a new constant and default value for the profile name:

public class CPConstants
{ 
    ...
    /////////////////////////////////////////////////
    //   Profile properties
    /////////////////////////////////////////////////
    /** Name of the current profile (if any). */
    public static final String  PROFILE_NAME_PN         = "profileName";
    /** Default value of profile name */
    public static final String  PROFILE_NAME_DV         = "default";
    ...
}

The Profile Class

An object of this class will act as a repository for configuration data for the main window of the Cartesian plane application where the graph appears. The data include:

  • The name of the profile.
  • The grid unit (the number of pixels allocated to one unit on the axes).
  • The properties encapsulated by the GraphPropertySet for the main window:
    • The width (note that this property is not dynamically applied to the main window).
    • The background color.
    • The foreground color (the color for displaying text).
    • The name of the font family used to display text.
    • The font size.
    • Whether or not the font is tagged as bold.
    • Whether or not the font is tagged as italic.
    • The draw property (the Boolean value that dictates whether or not major tics are to be labeled).
  • The properties encapsulated by the LinePropertySets for the axes, major tics, minor tics, and grid lines:
    • The draw property (which dictates whether or not the associated line set is to be displayed).
    • The stroke (the width of a line in a given line set).
    • The length.
    • The spacing (the number of lines per unit).
    • The color.

There is a companion facility for saving and restoring a profile’s properties; see Cartesian Plane Lesson 18 Page 4: ProfileParser.

⏹ Class and Instance Variables
The state needed to encapsulate the properties listed above requires a small number of class and instance variables. An annotated list follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Profile
{
    private static final PropertyManager        pMgr                =
        PropertyManager.INSTANCE;
    
    private static final 
    List<Class<? extends LinePropertySet>>      linePropertyClasses =
        List.of( 
            LinePropertySetAxes.class,
            LinePropertySetGridLines.class,
            LinePropertySetTicMajor.class,
            LinePropertySetTicMinor.class
        );
    
    private final GraphPropertySet              mainWindow          =
        new GraphPropertySetMW();
    private final Map<String,LinePropertySet>   linePropertySetMap  =
        new HashMap<>();
    private float                               gridUnit;
    private String                              name;
  • Lines 3,4: Convenient declaration of the PropertyManager singleton.
  • Lines 6-13: List of all concrete subclasses of LinePropertySet.
  • Lines 15,16: The GraphPropertySet for the main window.
  • Lines 17,18: Map where the key is the simple name of a concrete subclass of LinePropertySet, and the value is an instance of the named LinePropertySet. See putClass below.
  • Line 19: Grid unit property.
  • Line 20: Profile name property. Initialized in the constructor to “default.”

⏹ Private Methods
The following are the helper methods used to initialize the state of an object of this class.

🟦 private void putClass( Class<? extends LinePropertySet> clazz )
Given a Class object for a concrete LinePropertySet subclass, this method instantiates the given class and adds it to the LinePropertySetMap. A listing and notes follow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void putClass( Class<? extends LinePropertySet> clazz )
{
    try
    {
        String          name    = clazz.getSimpleName();
        LinePropertySet set     = 
            clazz.getDeclaredConstructor().newInstance();
        linePropertySetMap.put( name, set );
    }
    catch ( 
        NoSuchMethodException | 
        SecurityException |
        InvocationTargetException |
        IllegalAccessException |
        InstantiationException exc
    )
    {
        String  msg =
            "Could not instantiate " + clazz.getSimpleName();
        throw new ComponentException( msg );
    }
}
  • Lines 3-9: This try block is necessary because of the checked exceptions thrown by Class.getDeclaredConstructor() and Constructor.newInstance():
    • Line 5: Get the name of the given class.
    • Lines 6,7: Create an instance of the given class.
    • Line 8: Add the name/instance pair to the linePropertySetMap.
  • Lines 10-21: Process the exceptions the try block could produce. None of these exceptions should ever be thrown; if one is, it should be treated as a fatal program failure.

⏹ Constructor
The constructor for this class initializes the gridUnit and name instance variables and utilizes putClass to initialize the linePropertySetMap. Here’s the annotated code.

1
2
3
4
5
6
7
8
public Profile()
{
    gridUnit = pMgr.asFloat( CPConstants.GRID_UNIT_PN );
    name = pMgr.asString( CPConstants.PROFILE_NAME_PN );
    // mainWindow is initialized in its declaration
    linePropertyClasses.stream()
        .forEach( this::putClass );
}
  • Line 3: Reads the gridUnit property from the PropertyManager.
  • Line 4: Gives the profile a default name.
  • Line 6: Streams the list of Class objects for the concrete subclasses of LinePropertySet (see Class and Instance Variables).
  • Line 7: Calls putClass for each Class object.
  • Note that the mainWindow instance variable is initialized in its declaration.

⏹ Public Methods
To manage this class’s properties, we have public methods to access the data and to reset or apply the encapsulated profile. When users wish to change a property in the GraphPropertySet or LinePropertySets, they typically obtain and change the encapsulating object directly, for example:
    propSet = profile.getLinePropertySet( "LinePropertySetAxes" );
    propSet.setStroke( 3 );

We also have setters and getters for the gridUnit and name properties. The public methods are listed below.

🟦 public Float getGridUnit()
🟦 public void setGridUnit( float gridUnit )
🟦 public String getName()
🟦 public void setName( String name )
These methods get and set the gridUnit and name properties.

public Float getGridUnit()
{
    return gridUnit;
}
public void setGridUnit( float gridUnit )
{
    this.gridUnit = gridUnit;
}
public String getName()
{
    return name;
}
public void setName( String name )
{
    this.name = name;
}

🟦 public GraphPropertySet getMainWindow()
This method returns the GraphPropertySet for the main window.

public GraphPropertySet getMainWindow()
{
    return mainWindow;
}

🟦 public LinePropertySet getLinePropertySet( String simpleName )
This method returns the concrete LinePropertySet subclass with the given name. An example of invoking it looks like this:
    String simpleName = LinePropertySetTicMajor.class.getSimpleName();
    LinePropertySet set = workingProfile.getLinePropertySet( simpleName );

public LinePropertySet getLinePropertySet( String simpleName )
{
    LinePropertySet set = linePropertySetMap.get( simpleName );
    return set;
}

🟦 public void reset()
🟦 public void apply()
These methods reset the profile (refresh all properties from the PropertyManager) or apply the most recent changes (update the PropertyManager). The methods are straightforward because the GraphPropertySet and LinePropertySet objects have reset and apply facilities. See also Constructor.

public void reset()
{
    gridUnit = pMgr.asFloat( CPConstants.GRID_UNIT_PN );
    name = pMgr.asString( CPConstants.PROFILE_NAME_PN );
    mainWindow.reset();
    linePropertySetMap.values().forEach( s -> s.reset() );
}
public void apply()
{
    pMgr.setProperty( CPConstants.GRID_UNIT_PN, gridUnit );
    pMgr.setProperty( CPConstants.PROFILE_NAME_PN, name );
    mainWindow.apply();
    linePropertySetMap.values().forEach( s -> s.apply() );
}

🟦 public boolean equals( Object other )
🟦 public int hashCode()
These methods override the equals and hashCode methods in the Object class. Two Profile objects are equal if they have equal gridUnits, equal names, and all encapsulated property sets are equal. The hashCode method is overridden as is required whenever a class overrides equals. The code follows.

@Override
public int hashCode()
{
    int hashCode    =
        Objects.hash( name, gridUnit, mainWindow, linePropertySetMap );
    return hashCode;
}

@Override
public boolean equals( Object other )
{
    boolean result  = false;
    if ( this == other )
        result = true;
    else if ( other == null )
        result = false;
    else if ( getClass() != other.getClass() )
        result = false;
    else
    {
        Profile that    = (Profile)other;
        result          =
            this.name.equals( that.name )
            && this.gridUnit == that.gridUnit
            && this.mainWindow.equals( that.mainWindow )
            && this.linePropertySetMap.equals( that.linePropertySetMap );
    }
    return result;
}

The ProfileUtils Class

We have developed the ProfileUtils class in the test_utils package under the test source tree to assist with writing JUnit tests for the Profile class. Currently, it has a single public class methodpublic static Profile getDistinctProfile(Profile srcProfile). This method takes a Profile object as input and produces a Profile object with property values distinct from the input. This allows us to write tests such as this one:

Profile baseProfile = new Profile();
Profile distinctProfile = ProfileUtils.getDistinctProfile( baseProfile );
Profile testProfile = new Profile();
// verify that testProfile matches baseProfile
// change the values in testProfile to those in distinctProfile
// verify that testProfile matches distinctProfile
testProfile.reset();
// verify that testProfile matches baseProfile

🟦 Private Methods
Following is a description of the helper methods contained in the ProfileUtils class.

private static String getDistinctFontName( String nameIn )
This method produces a valid font name that is distinct from the input. Here’s the code.

private static String getDistinctFontName( String nameIn )
{
    String  nameOut = nameIn.equals( Font.MONOSPACED ) ? 
        Font.DIALOG : Font.MONOSPACED;
    return nameOut;
}

private static Color getDistinctColor( Color colorIn )
This method produces a color that is distinct from the input. As an example, if the input Color.BLUE (0x0000FF), the output is new Color(0xFFFF00). Here’s the code.

private static Color getDistinctColor( Color colorIn )
{
    int     iColorIn    = colorIn.getRGB() & 0xFFFFFF;
    int     iColorOut   = ~iColorIn & 0xFFFFFF;
    Color   colorOut    = new Color( iColorOut );
    return colorOut;
}

private static void getDistinctProperties( Profile destProfile, GraphPropertySet src )
This method takes the properties in src, calculates distinct values, and stores the new values in destProfile. A source listing for this method follows.

private static void 
getDistinctProperties( Profile destProfile, GraphPropertySet src )
{
    GraphPropertySet    dest    =   destProfile.getMainWindow();
    
    float   newWidth    = src.getWidth() + 10;
    Color   newBGColor  = getDistinctColor( src.getBGColor() );
    Color   newFGColor  = getDistinctColor( src.getFGColor() );
    String  newName     = getDistinctFontName( src.getFontName() );
    boolean newBold     = !src.isBold();
    boolean newItalic   = !src.isItalic();
    float   newSize     = (int)src.getFontSize() + 10;
    boolean newDraw     = !src.isFontDraw();
    
    dest.setWidth( newWidth );
    dest.setBGColor( newBGColor );
    dest.setFGColor( newFGColor );
    dest.setFontName( newName );
    dest.setFontSize( newSize );
    dest.setBold( newBold );
    dest.setItalic( newItalic );
    dest.setFontDraw( newDraw );
}

private static void getDistinctProperties( Profile destProfile, LinePropertySet src )
For the given LinePropertySet, this method calculates distinct values for all properties and stores them in the corresponding LinePropertySet object in the destination profile. Here’s the code.

private static void
getDistinctProperties( Profile destProfile, LinePropertySet src )
{
    String          setName = src.getClass().getSimpleName();
    LinePropertySet dest    = destProfile.getLinePropertySet( setName );
    boolean newDraw     = !src.getDraw();
    float   newStroke   = src.getStroke() + 10;
    float   newLength   = src.getLength() + 10;
    float   newSpacing  = src.getSpacing() + 10;
    Color   newColor    = getDistinctColor( src.getColor() );
    
    dest.setDraw( newDraw );
    dest.setStroke( newStroke );
    dest.setLength( newLength );
    dest.setSpacing( newSpacing );
    dest.setColor( newColor );
}

🟦 Public Methods
At least for now, this utility class has a single public class method. It is discussed below.

public static Profile getDistinctProfile( Profile srcProfile )
From the given Profile, this method creates a new Profile with properties guaranteed to differ from the input. Here’s a listing of the method.

public static Profile getDistinctProfile( Profile srcProfile )
{
    final String[] propSetNames =
    {
        LinePropertySetAxes.class.getSimpleName(),
        LinePropertySetGridLines.class.getSimpleName(),
        LinePropertySetTicMajor.class.getSimpleName(),
        LinePropertySetTicMinor.class.getSimpleName(),
    };
    
    Profile destProfile = new Profile();
    destProfile.setName( srcProfile.getName() + "_MUTATE" );
    destProfile.setGridUnit( srcProfile.getGridUnit() + 1 );
    getDistinctProperties( destProfile, srcProfile.getMainWindow() );
    
    Stream.of( propSetNames )
        .map( srcProfile::getLinePropertySet )
        .forEach( set -> getDistinctProperties( destProfile, set ) );
    return destProfile;
}

Profile Class JUnit Test

The Profile class encapsulates the gridUnit and name properties, the properties in the main window GraphPropertySet, and the properties in each of the concrete LinePropertySet subclasses. The JUnit test for the Profile class, ProfileTest, concentrates on validating the following:

  • The ability to set and get the profile name and grid unit;
  • The ability to commit the profile name and grid unit via the apply method;
  • The ability to set and get a property;
  • The ability to change a property and then reset it to its original value;
  • The ability to change a property and apply the change to the PropertyManager;
  • The ability to test two Profiles for equality.

The ProfileTest class is discussed below.

🟦 Infrastructure
Here is an annotated listing of the class and instance variables encapsulated in the ProfileTest class. Included is a listing of the BeforeEach and AfterEach methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ProfileTest
{
    private static final String[]   linePropertySetClasses  =
    {
        LinePropertySetAxes.class.getSimpleName(),
        LinePropertySetGridLines.class.getSimpleName(),
        LinePropertySetTicMajor.class.getSimpleName(),
        LinePropertySetTicMinor.class.getSimpleName()
    };
    
    private final Profile   protoProfile    = new Profile();
    private final Profile   distinctProfile = 
        ProfileUtils.getDistinctProfile( protoProfile );
    
    private Profile workingProfile;
    
    @BeforeEach
    public void beforeEach() throws Exception
    {
        workingProfile = new Profile();
    }
    
    @AfterEach
    public void afterEach()
    {
        protoProfile.apply();
    }
    // ...
  • Lines 3-9: Array of the names of the LinePropertySet concrete subclasses.
  • Line 11: Prototype Profile. This Profile is instantiated at the start of the application and never modified. It represents a snapshot of Profile properties as initialized in the PropertyManager. After each test, it is used to restore the PropertyManager to its initial state (lines 23-27).
  • Lines 12,13: This is a set of distinct Profile properties. It is created at the start of the test and never changed. The value of each property in this object is calculated to be different from the corresponding property in protoProfile (line 11). The encapsulated values are used to test setters and getters, as well as the equals method.
  • Line 15: This profile will be used as needed during any test and may be reinitialized anytime. It is restored to default values in the beforeEach (lines 17-21) method.
  • Lines 17-21: @BeforeEach method; initializes the workingProfile before each test.
  • Lines 23-27: @AfterEach method; restores the PropertyManager to its original values after each test.

🟦 Helper Methods
Following is a description of the helper methods in the ProfileTest class.

private void testProperty(Function<Profile,Object> getter,BiConsumer<Profile,Object> setter)
This method is used to manipulate a single property encapsulated in a Profile. The caller provides a setter and a getter for the target property. Following are three examples of its use; the first is used to test the gridUnit property, the second the fontSize property of the main window GraphPropertySet, and the third to test the stroke property of a LinePropertySet:

testProperty(
    p -> p.getGridUnit(),
    (p,v) -> p.setGridUnit( (float)v )
);

testProperty(
    p -> p.getMainWindow().getFontSize(),
    (p,v) -> p.getMainWindow().setFontSize( (float)v )
);

// Given: name is one of LinePropertySetAxes, LinePropertyGridLines,
// LinePropertySetTicMajor, or LinePropertySetTicMinor.
testProperty(
    p -> p.getLinePropertySet( name ).getStroke(),
    (p,v) -> p.getLinePropertySet( name ).setStroke( (float)v )
);

The testProperty method uses the getter to obtain the current value of a Profile property and an alternative, unique value to test changes to the property. It uses the setter to make changes to the workingProfile during the test. For example:

Object  protoVal    = getter.apply( protoProfile );
Object  distinctVal = getter.apply( distinctProfile );
Object  workingVal  = getter.apply( workingProfile );
setter.accept( workingProfile, distinctVal );

Here is an annotated listing of the testProperty method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void testProperty(
    Function<Profile,Object> getter,
    BiConsumer<Profile,Object> setter
)
{
    protoProfile.apply();
    workingProfile = new Profile();
        
    Object  protoVal    = getter.apply( protoProfile );
    Object  distinctVal = getter.apply( distinctProfile );
    Object  workingVal  = getter.apply( workingProfile );
    assertEquals( protoVal, workingVal );
    assertNotEquals( protoVal, distinctVal );
    
    setter.accept( workingProfile, distinctVal );
    assertEquals( distinctVal, getter.apply( workingProfile ) );
    
    workingProfile.reset();
    assertEquals( protoVal, getter.apply( workingProfile ) );
    
    setter.accept( workingProfile, distinctVal );
    workingProfile.apply();
    assertEquals( distinctVal, getter.apply( workingProfile ) );
    
    workingProfile.reset();
    assertEquals( distinctVal, getter.apply( workingProfile ) );
}
  • Line 6: Force the PropertyManager into its initial state.
  • Line 7: Initialize the workingProfile; it should now have the same property values as protoProfile and the PropertyManager.
  • Lines 9-11: Get the values of the target property from the protoProfile, distinctProfile, and workingProfile.
  • Line 12: Sanity check; verify that the property values in the protoProfile and workingProfile are equal.
  • Line 13: Sanity check; verify that the values of the property in the protoProfile and distinctProfile are different
  • Line 15: Change the value of the property in the workingProfile.
  • Line 16: Verify the change made on line 15.
  • Line 18: Restore the workingProfile property to its original value.
  • Line 19: Verify that the value of the target property has been restored.
  • Lines 21,22: Change the value of the target property in the workingProfile and apply it to the PropertyManager.
  • Line 23: Verify the value of the property in the workingProfile is the same as that in the distinceProfile.
  • Lines 25,26: Re-initialize the workingProfile from the PropertyManager and verify that the change made at line 21 was correctly applied at line 22.

private void testEqualsByField( Consumer<Profile> mutator )
This method is used in the verification of the equals method. Two identical Profiles are created and tested for equality. The mutator is then used to change the value of a property in one of the Profiles, and the inequality of the Profiles is verified. The strategy is to invoke this method with a mutator for the grid unit, a mutator for the name, a mutator for a property of the encapsulated GraphPropertySet, and a mutator for a property in each of the encapsulated LinePropertySets. It is not necessary to verify the result of changing every method in the GraphPropertySet and LinePropertySets; that’s the job of the JUnit tests for GraphPropertySet and LinePropertySet. Here are three examples of its use; the first is used to test the grid unit property, the second to test the fontSize property of the main window GraphPropertySet, and the third to test the stroke property of a LinePropertySet:

    float   mutatedGridUnit = distinctProfile.getGridUnit();
    testEqualsByField( p -> p.setGridUnit( mutatedGridUnit ) );

    float   mutatedFontSize = 
        distinctProfile.getMainWindow().getFontSize();
    testEqualsByField( p -> 
        p.getMainWindow().setFontSize( mutatedFontSize )
    );

    // Given: name is one of LinePropertySetAxes, 
    // LinePropertySetGridLines,
    // LinePropertySetTicMajor, or LinePropertySetTicMinor.
    LinePropertySet set = 
        distinctProfile.getLinePropertySet( name );
    float   stroke  = set.getStroke();
    testEqualsByField( 
        p -> p.getLinePropertySet( name ).setStroke( stroke )
    );

Here is a listing of the testEqualsByField method, followed by a few notes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
private void testEqualsByField( Consumer<Profile> mutator )
{
    Profile profile1    = new Profile();
    Profile profile2    = new Profile();
    assertFalse( profile1.equals( null ) );
    assertFalse( profile1.equals( new Object() ) );
    assertTrue( profile1.equals( profile1 ) );
    assertTrue( profile1.equals( profile2 ) );
    assertTrue( profile2.equals( profile1 ) );
    assertEquals( profile1.hashCode(), profile2.hashCode() );
    
    mutator.accept( profile2 );
    assertFalse( profile1.equals( profile2 ) );
    assertFalse( profile2.equals( profile1 ) );
    
    mutator.accept( profile1 );
    assertTrue( profile1.equals( profile2 ) );
    assertTrue( profile2.equals( profile1 ) );
    assertEquals( profile1.hashCode(), profile2.hashCode() );
}
  • Lines 3,4: Create two equal Profiles.
  • Line 5: Verify that equals returns false when passed a null argument.
  • Line 6: Verify that equals returns false when passed a non-Profile argument.
  • Line 7: Verify reflexivity of equals.
  • Line 8: Verify that the profiles created at lines 3 and 4 are equal.
  • Line 9: Verify symmetry of equals.
  • Line 10: Verify that the hashcodes for the Profiles are equal.
  • Line 12: Change the value of the target property in profile2 to a unique value.
  • Line 13: Verify the inequality of the Profiles.
  • Line 14: Verify symmetry of equals.
  • Line 16: Change profile1 the same way profile2 was changed on line 12.
  • Line 17: Verify the equality of the Profiles.
  • Line 18: Verify symmetry of equals.
  • Line 19: Verify that the hashcodes for the Profiles are equal.

🟦Principal Test Methods
Following is a discussion of the principal test methods in the ProfileTest class.

@Test public void testName()
Validate operations with the name property in the context of its setter and getter and the Profile’s apply and reset facilities. The code for this method follows.

@Test
public void testName()
{
    testProperty(
        p -> p.getName(),
        (p,v) -> p.setName( (String)v )
    );
}

@Test public void testGridUnit()
Test the gridUnit behavior in the context of its setter and getter and the Profile’s apply and reset facilities. The code for this method follows.

@Test
public void testGridUnit()
{
    testProperty(
        p -> p.getGridUnit(),
        (p,v) -> p.setGridUnit( (float)v )
    );
}

public void testGetMainWindow()
Test the getter for the main window GraphPropertySet. Here’s the code.

@Test
public void testGetMainWindow()
{
    GraphPropertySet    expSet  = protoProfile.getMainWindow();
    GraphPropertySet    actSet  = workingProfile.getMainWindow();
    assertEquals( expSet, actSet );
}

public void testMainWindowProperties()
Test the behavior of the main window properties in the context of their setters and getters and the Profile’s apply and reset facilities. Here’s an abbreviated listing for this method; the complete code can be found in the GitHub repository.

@Test
public void testMainWindowProperties()
{
    testProperty(
        p -> p.getMainWindow().getWidth(),
        (p,v) -> p.getMainWindow().setWidth( (float)v )
    );

    testProperty(
        p -> p.getMainWindow().getBGColor(),
        (p,v) -> p.getMainWindow().setBGColor( (Color)v )
    );
    ...
    testProperty(
        p -> p.getMainWindow().isFontDraw(),
        (p,v) -> p.getMainWindow().setFontDraw( (boolean)v )
    );
}

public void testGetLinePropertySet( String name )
This parameterized test verifies the behavior of all the properties of the LinePropertySets in the context of their setters and getters and the Profile’s apply and reset facilities. Properties not supported in a given LinePropertySet are skipped. Here’s an abbreviated listing for this method; the complete code can be found in the GitHub repository.

@ParameterizedTest
@ValueSource(strings= {
    "LinePropertySetAxes",
    "LinePropertySetGridLines",
    "LinePropertySetTicMajor",
    "LinePropertySetTicMinor",
    }
)
public void testGetLinePropertySet( String name )
{
    LinePropertySet set = protoProfile.getLinePropertySet( name );
    
    if ( set.hasDraw() )
        testProperty(
            p -> p.getLinePropertySet( name ).getDraw(),
            (p,v) -> p.getLinePropertySet( name ).setDraw( (boolean)v )
        );

    if ( set.hasStroke() )
        testProperty(
            p -> p.getLinePropertySet( name ).getStroke(),
            (p,v) -> p.getLinePropertySet( name ).setStroke( (float)v )
        );
    // ...
    if ( set.hasColor() )
        testProperty(
            p -> p.getLinePropertySet( name ).getColor(),
            (p,v) -> p.getLinePropertySet( name ).setColor( (Color)v )
        );
}

public void testEquals()
This method verifies the behavior of the equals method. The test is conducted under four different scenarios:

  • Start with two equal profiles and verify they become unequal when the gridUnit property changes.
  • Start with two equal profiles and verify they become unequal when the name property changes.
  • Start with two equal profiles and verify that they become unequal when a property in the main window GraphPropertySet changes.
  • For each LinePropertySet encapsulated in a Profile:
    • Start with two equal profiles and verify that they become unequal when a property in the LinePropertySet changes.

We don’t have to validate the equals method for every property in the GraphPropertySet and LinePropertySet classes. Field-by-field testing is the responsibility of the test methods for those classes. For the Profile class, we need only demonstrate that if any corresponding property sets are unequal, then the Profile objects that contain them are unequal.

Here’s an annotated listing of the testEquals method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testEquals()
{
    String   mutatedName    = distinctProfile.getName();
    testEqualsByField( p -> p.setName( mutatedName ) );
    float   mutatedGridUnit = distinctProfile.getGridUnit();
    testEqualsByField( p -> p.setGridUnit( mutatedGridUnit ) );
    float   mutatedFontSize = 
        distinctProfile.getMainWindow().getFontSize();
    testEqualsByField( p -> 
        p.getMainWindow().setFontSize( mutatedFontSize ) 
    );
    Stream.of( linePropertySetClasses )
        .forEach( s -> {
            LinePropertySet set = 
                distinctProfile.getLinePropertySet( s );
            float   stroke  = set.getStroke();
            testEqualsByField( 
                p -> p.getLinePropertySet( s ).setStroke( stroke )
            );
        });
}
  • Lines 4,5: Test the equals method when two Profiles are equal and differ in just the value of the name property.
  • Lines 6,7: Test the equals method when two Profiles are equal and when they differ in just the value of the gridUnit property.
  • Lines 8-12: Test the equals method when two Profiles are equal and differ in just the value of the fontSize property of the main window GraphPropertySet.
  • Lines 13-21: For the name of each encapsulated LinePropertySet:
    • Lines 15-17: Get the unique value for the stroke property of a given LinePropertySet.
    • Lines 18-20: Test the equals method when two Profiles are equal and when they differ in just the value of the stroke property of the given LinePropertySet.

Summary

On this page, we wrote and tested the Profile class, which encapsulates all the properties used to configure the appearance of the main window of our Cartesian plane display. On the next page, we’ll look at a facility for saving and restoring those properties.

Next: The ProfileParser Class