Friday, February 19, 2016

AngularJS Custom Directive: Display Elwood Build Result Stats

I've written my first successful custom AngularJS directive to display build result stats (see issue #508680).

The directive initially displays the build status indicating success, failure or in-progress. Adjacent to this is a clickable icon1 that goes back to the server and retrieve the build result statistics and displays them on the screen.

Display elwood build result stats

The journey to get here is challenging  (especially for someone who works at the back office) but very fulfilling except for one last thing - unit testing.


Below is the directive code:

angular.module('elwoodUiApp')
  .constant('BuildResultStatsUrl', 'http://localhost:8080/buildResultStats/:key/:count')
  .factory('BuildResultStatsResource', function($resource, BuildResultStatsUrl) {
    return $resource(BuildResultStatsUrl, {}, {
      'get': {method: 'GET'}
    });
  })
  .directive('elwBuildResultStats', function() {
    var controller = function($scope, BuildResultStatsResource, ToKeyCount) {
      $scope.toggle = false;
      $scope.getBuildResultStats = function(keyCount) {
        BuildResultStatsResource.get({'key': keyCount.key, 'count': keyCount.count},
          function (successResult) {
            console.log(successResult);
            if (!$scope.buildResultStats) {
              $scope.buildResultStats = [];
              $scope.buildResultStats[ToKeyCount(keyCount)] = {
                'successCount': successResult.successCount,
                'failedCount': successResult.failedCount,
                'ignoredCount': successResult.ignoredCount
              };
            }
            $scope.toggle = !$scope.toggle;
          }, function (errorResult) {
            console.log(errorResult);
          }
        );
      };

      $scope.isShowBuildResultStats = function(keyCount) {
        return $scope.toggle
          && ($scope.buildResultStats
            && ($scope.status == 'SUCCEEDED' || $scope.status === 'FAILED'));
      }
    };

    return {
      restrict: 'A',
      templateUrl: 'views/buildresultstats.html',
      controller: controller,
      scope: {
        status: '@',
        keyCount: '=',
      }
    };
  });


And below is the test code:

describe('Controller: BuildResultStatsCtrl', function () {

  // load the controller's module
  beforeEach(module('elwoodUiApp'));

  var
    scope,
    httpBackend;

  describe('BuildResultStatsCtrl', function() {
    var BuildResultStatsCtrl,
      buildResultStatsResource,
      toKeyCount;

    // Initialize the controller and a mock scope
    beforeEach(inject(function ($controller, $rootScope, $compile, $httpBackend, BuildResultStatsResource, ToKeyCount) {
      scope = $rootScope.$new();
      httpBackend = $httpBackend;
      buildResultStatsResource = BuildResultStatsResource;
      toKeyCount = ToKeyCount;

      var elem = '<div elw-build-result-stats="" key-count="keyCountTuple" status="{{buildStatus}}"></div>';
      scope.status = 'SUCCEEDED';
      scope.keyCount = {
        key: 'PRJ',
        count: '10'
      };

      httpBackend.whenGET('views/buildresultstats.html').respond(200, '');
      var template = $compile(elem)(scope, buildResultStatsResource, toKeyCount);
      console.log(template);

      scope.$digest();
    }));

    it('should display when status is SUCCEEDED', function() {
      var url = 'http://localhost:8080/buildResultStats/PRJ/10';

      expect(scope).toBeDefined();
      expect(scope.toggle).toBeFalsy();
      expect(scope.isShowBuildResultStats).toBeFalsy();
      expect(scope.buildResultStats).toBeUndefined();

      var mockData = {
        successCount: 10,
        failedCount: 3,
        ignoredCount: 2
      };

      httpBackend.expectGET(url).respond(mockData);

      expect(buildResultStatsResource).toBeDefined();
      scope.getBuildResultStats(scope.keyCount);
      httpBackend.flush();

      expect(scope.toggle).toBeTruthy();
    });
  });
});


After compiling the element at line number 30, I'm expecting the scope variables and functions will be fully defined and by the time I invoke "scope.getBuildResultStats(scope.keyCount)" the mocked GET should return the mock data.

Well, it looks like it didn't materialise for some unknown reason.


It's almost quarter past 2 in the morning.

Changes aren't committed yet.

I feel very tired but I think I'm almost there...

Update: (2016-02-23 12:10 AM) Extracting the directive's function into a full blown controller and referring the controller with a controller name might be an option. This allows me to test the controller as an standalone component outside of this directive.

.controller('BuildResultStatsCtrl', function($scope, BuildResultStatsResource, ToKeyCount) {
  // ...
}

return {
  restrict: 'A',
  templateUrl: 'views/buildresultstats.html',
  controller: 'BuildResultStatsCtrl',
  scope: {
    status: '@',
    keyCount: '=',
  }
};


1Generated from http://www.amp-what.com and I find this website very cool.

Saturday, February 6, 2016

Java 7 and Multi-Catch Exception

Java 7 has been out for a few years and I admit this is one of the features I seem to neglect. Perhaps, I've been living under a rock, yeah?

I've got JDK 8 installed and because of my sheer laziness, I'll stick to this version of the compiler to analyse how multi-catch exception is implemented.

She saw first some bracelets, then a pearl necklace, then a Venetian gold cross set with precious stones, of admirable workmanship. She tried on the ornaments before the mirror, hesitated and could not make up her mind to part with them, to give them back.

She kept asking: "Haven't you any more?"

Let's define a few exceptions before dipping a toe in the water.

PearlNecklaceException.java
package org.lyeung.thenecklace;

public class PearlNecklaceException extends RuntimeException {
    // do-nothing
}

VenetianCrossException.java
package org.lyeung.thenecklace;

public class VenetianCrossException extends RuntimeException {
    // do-nothing
}

DiamondNecklaceException.java
package org.lyeung.thenecklace;

public class DiamondNecklaceException extends RuntimeException {
    // do-nothing
}

Followed by the main class we'll look at:
Mathilde.java
package org.lyeung.thenecklace;

public class Mathilde {

    private void retrieveNecklace(int value) throws PearlNecklaceException,
            VenetianCrossException, DiamondNecklaceException {
        if (value == 0) {
            throw new PearlNecklaceException();
        } else if (value == 1) {
            throw new VenetianCrossException();
        } else {
            throw new DiamondNecklaceException();
        }
    }

    public void findNecklace1(int value) {
        try {
            retrieveNecklace(value);
        } catch (PearlNecklaceException e) {
            e.printStackTrace();
        } catch (VenetianCrossException e) {
            e.printStackTrace();
        } catch (DiamondNecklaceException e) {
            e.printStackTrace();
        }
    }

    public void findNecklace2(int value) {
        try {
            retrieveNecklace(value);
        } catch (PearlNecklaceException e) {
            handleException(e);
        } catch (VenetianCrossException e) {
            handleException(e);
        } catch (DiamondNecklaceException e) {
            handleException(e);
        }
    }

    private void handleException(Exception e) {
        e.printStackTrace();
    }

    public void findNecklace3(int value) {
        try {
            retrieveNecklace(value);
        } catch (VenetianCrossException
                | PearlNecklaceException
                | DiamondNecklaceException  e) {
            e.printStackTrace();
        }
    }
}


All three variants of find necklaces call retrieveNecklace which throws different exceptions base on input value.

The first variant of find necklace is pretty old school. We declare different catch blocks for each exception thrown.

The second variant of find necklace, similar to the first variant, but minimises code duplication by moving the print stack trace into a common method.

The third variant uses multi-catch exception that results to a more readable and more compact code.

Under the hood, these variants produce different bytecodes.


The disassembled code for the first variant:
public void findNecklace1(int);
  Code:
     0: aload_0
     1: iload_1
     2: invokespecial #8                  // Method retrieveNecklace:(I)V
     5: goto          29
     8: astore_2
     9: aload_2
    10: invokevirtual #9                  // Method org/lyeung/thenecklace/PearlNecklaceException.printStackTrace:()V
    13: goto          29
    16: astore_2
    17: aload_2
    18: invokevirtual #10                 // Method org/lyeung/thenecklace/VenetianCrossException.printStackTrace:()V
    21: goto          29
    24: astore_2
    25: aload_2
    26: invokevirtual #11                 // Method org/lyeung/thenecklace/DiamondNecklaceException.printStackTrace:()V
    29: return
  Exception table:
     from    to  target type
         0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
         0     5    16   Class org/lyeung/thenecklace/VenetianCrossException
         0     5    24   Class org/lyeung/thenecklace/DiamondNecklaceException

The exception table says,  "For program counter offset 0 to 5, inclusive and exclusive respectively, the program counter should jump to target offset 8 if PearlNecklaceException is thrown, 16 if VenetianCrossException is thrown and 24 if DiamondNecklaceException is thrown."

All offsets 8, 16 and 24 invoke the caught exception's printStackTrace() method.

The disassembled code for the second variant:
public void findNecklace2(int);
  Code:
     0: aload_0
     1: iload_1
     2: invokespecial #8                  // Method retrieveNecklace:(I)V
     5: goto          32
     8: astore_2
     9: aload_0
    10: aload_2
    11: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    14: goto          32
    17: astore_2
    18: aload_0
    19: aload_2
    20: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    23: goto          32
    26: astore_2
    27: aload_0
    28: aload_2
    29: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    32: return
  Exception table:
     from    to  target type
         0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
         0     5    17   Class org/lyeung/thenecklace/VenetianCrossException
         0     5    26   Class org/lyeung/thenecklace/DiamondNecklaceException

The exception table is not much different from the first variant.

The disassembled code for the last variant:
public void findNecklace3(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          13
       8: astore_2
       9: aload_2
      10: invokevirtual #14                 // Method java/lang/RuntimeException.printStackTrace:()V
      13: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/VenetianCrossException
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5     8   Class org/lyeung/thenecklace/DiamondNecklaceException
}

Now, the exception table looks different. As you can see, the target values all point at 8, where offset 8 prints the stack trace!

The generated bytecodes are definitely smaller and more compact than the previous variants.


Below is the disassembled code for entire Mathilde class:
Compiled from "Mathilde.java"
public class org.lyeung.thenecklace.Mathilde {
  public org.lyeung.thenecklace.Mathilde();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  private void retrieveNecklace(int) throws org.lyeung.thenecklace.PearlNecklaceException, org.lyeung.thenecklace.VenetianCrossException, org.lyeung.thenecklace.DiamondNecklaceException;
    Code:
       0: iload_1
       1: ifne          12
       4: new           #2                  // class org/lyeung/thenecklace/PearlNecklaceException
       7: dup
       8: invokespecial #3                  // Method org/lyeung/thenecklace/PearlNecklaceException."":()V
      11: athrow
      12: iload_1
      13: iconst_1
      14: if_icmpne     25
      17: new           #4                  // class org/lyeung/thenecklace/VenetianCrossException
      20: dup
      21: invokespecial #5                  // Method org/lyeung/thenecklace/VenetianCrossException."":()V
      24: athrow
      25: new           #6                  // class org/lyeung/thenecklace/DiamondNecklaceException
      28: dup
      29: invokespecial #7                  // Method org/lyeung/thenecklace/DiamondNecklaceException."":()V
      32: athrow

  public void findNecklace1(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          29
       8: astore_2
       9: aload_2
      10: invokevirtual #9                  // Method org/lyeung/thenecklace/PearlNecklaceException.printStackTrace:()V
      13: goto          29
      16: astore_2
      17: aload_2
      18: invokevirtual #10                 // Method org/lyeung/thenecklace/VenetianCrossException.printStackTrace:()V
      21: goto          29
      24: astore_2
      25: aload_2
      26: invokevirtual #11                 // Method org/lyeung/thenecklace/DiamondNecklaceException.printStackTrace:()V
      29: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5    16   Class org/lyeung/thenecklace/VenetianCrossException
           0     5    24   Class org/lyeung/thenecklace/DiamondNecklaceException

  public void findNecklace2(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          32
       8: astore_2
       9: aload_0
      10: aload_2
      11: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      14: goto          32
      17: astore_2
      18: aload_0
      19: aload_2
      20: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      23: goto          32
      26: astore_2
      27: aload_0
      28: aload_2
      29: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      32: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5    17   Class org/lyeung/thenecklace/VenetianCrossException
           0     5    26   Class org/lyeung/thenecklace/DiamondNecklaceException

  private void handleException(java.lang.Exception);
    Code:
       0: aload_1
       1: invokevirtual #13                 // Method java/lang/Exception.printStackTrace:()V
       4: return

  public void findNecklace3(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          13
       8: astore_2
       9: aload_2
      10: invokevirtual #14                 // Method java/lang/RuntimeException.printStackTrace:()V
      13: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/VenetianCrossException
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5     8   Class org/lyeung/thenecklace/DiamondNecklaceException
}