Thursday, January 20, 2011

Specification Pattern in Dojo

A specification is a powerful design pattern able to tell if a candidate object matches some criteria. The specification has a method isSatisfiedBy(entity) that returns true if all criteria are met by entity.

In simple terms, a Specification is a small encapsulated unit of logic that gives an answer to a simple straight forward question: “does it match?”

Using a Specification we will split the logic of how we make a selection, away from objects we are selecting.

The Specification Pattern was originally proposed by Martin Fowler and Eric Evans.

Here is Dojo implementation of the CompositeSpecification.

/*
 * The Specification Pattern JavaScript Implementation
 *
 * A specification is a powerful design pattern able to tell if a candidate object
 * matches some criteria. The specification has a method isSatisfiedBy(entity)
 * that returns true if all criteria are met by entity
 *
 * @example http://www.martinfowler.com/apsupp/spec.pdf
 * @example http://en.wikipedia.org/wiki/Specification_pattern
 * @example http://devlicio.us/blogs/jeff_perrin/archive/2006/12/13/the-specification-pattern.aspx
 * @example http://devlicio.us/blogs/casey/archive/2009/03/02/ddd-the-specification-pattern.aspx
 *
 *
 * LICENSE: Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @author    Goran Miskovic, schkovich[at]gmail[dot]com
 * @copyright 2010 Goran Miskovic ©
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 * @version   SVN: $Id: CompositeSpecification.js 85 2011-03-02 22:26:46Z schkovich $
 */
dojo.provide("schkovich.CompositeSpecification");

dojo.declare("schkovich.CompositeSpecification", null, {
    constructor: function() {
        var AndSpecification = dojo.declare(schkovich.CompositeSpecification, {
            constructor: function(self, other) {
                var One = self;
                var Other = other;
                this.IsSatisfiedBy = function(entity) {
                    return One.IsSatisfiedBy(entity) && Other.IsSatisfiedBy(entity);
                }
            }
        });

        var OrSpecification = dojo.declare(schkovich.CompositeSpecification,  {
            constructor: function(self, other) {
                var One = self;
                var Other = other;
                this.IsSatisfiedBy = function(entity) {
                    return One.IsSatisfiedBy(entity) || Other.IsSatisfiedBy(entity);
                }
            }
        });

        var NotSpecification =  dojo.declare(schkovich.CompositeSpecification,  {
            constructor: function(self) {
                var Wrapped = self;
                this.IsSatisfiedBy = function(entity) {
                    return !Wrapped.IsSatisfiedBy(entity);
                }
            }
        });

        this.And = function(other) {
            return new AndSpecification(this, other);
        }

        this.Or = function(other) {
            return new OrSpecification(this, other);
        }

        this.Not = function() {
            return new NotSpecification(this);
        }
    }
})

We will setup very simple example. Imagine you are librarian and the students asks for a list of composers that where productive during Romantic or Post-Romantic epoch and have compositions in C-minor or G-major.

In order to simplify the example we will assume that those whose first name starts with letter C did compose something in C-minor and that those whose first name starts on G composed something in G-major. :)

Data are structured in this way:
{"id":"1","firstName":"Johann Sebastian","lastName":"Bach","category":"Baroque"}
First we will need to create epoch specification:
var EpochSpecification = dojo.declare(
                schkovich.CompositeSpecification, {
                    constructor: function(epoch) {
                        this.IsSatisfiedBy = function(entity) {
                            return entity.category
                                && (entity.category === epoch);
                        }
                    }
                });

Then we need logic to select ones having compositions in C-minor or G-major or as we assumed ones whose first name starts with letter C or G:
var FirstLetterSpecification = dojo.declare(
                schkovich.CompositeSpecification, {
                    constructor: function(firstLetter) {
                        this.IsSatisfiedBy = function(entity) {
                            return entity.firstName
                                && (entity.firstName.indexOf(firstLetter) === 0);
                        }
                    }
                });

Once we defined conditions we will put them in a single specification:
var spec = new EpochSpecification("Romantic").
                    Or(new EpochSpecification("Post-Romantic")).
                    And(
                        new FirstLetterSpecification("G")
                        .Or(new FirstLetterSpecification("C"))
                    );

Finally we need a function that will filter data:
var filterComposers = function(specification) {
                    var filtered = dojo.filter(composersData.items, function(entity){
                        return specification.IsSatisfiedBy(entity);
                    });
                    composersData.items = filtered;
                    return composersData;
                }

Here is the HTML page that puts all of the above together:
<!DOCTYPE html>
<html>
    <head>
        <title>The Dojo Toolkit:: The Specification Pattern</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet"
              type="text/css"
              href="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/claro/claro.css"
              />
        <style type="text/css">
            @import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/grid/resources/claroGrid.css";
            html, body {
                width: 100%;
                margin: 0;
                font-family:helvetica,arial,sans-serif; font-size:90%;
            }
            .dojoxGrid table {margin: 0;}
            #gridPlaceholder {height:314px; width: 50%}
        </style>
    </head>
    <body class="claro">
        <div id="gridPlaceholder"></div>
        <script type="text/javascript">
            djConfig={
                parseOnLoad: false,
                isDebug: false,
                baseUrl: "./",
                modulePaths: {schkovich: "./resources/schkovich" }
            };
        </script>
        <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js">
        </script>
        <script>
            dojo.require("dojo.data.ItemFileReadStore");
            dojo.require("dojox.grid.DataGrid");
            dojo.require("dijit.form.Button");
            dojo.require("schkovich.CompositeSpecification")

            dojo.addOnLoad(function() {
                dojo.parser.parse();
                var composersData = {
                    identifier: "id",
                    label: 'firstName',
                    items: [
                        {"id":"1","firstName":"Johann Sebastian","lastName":"Bach","category":"Baroque"},
                        {"id":"2","firstName":"Arcangelo","lastName":"Corelli","category":"Baroque"},
                        {"id":"3","firstName":"George Frideric","lastName":"Handel","category":"Baroque"},
                        {"id":"4","firstName":"Henry","lastName":"Purcell","category":"Baroque"},
                        {"id":"5","firstName":"Jean-Philippe","lastName":"Rameau","category":"Baroque"},
                        {"id":"6","firstName":"Domenico","lastName":"Scarlatti","category":"Baroque"},
                        {"id":"7","firstName":"Antonio","lastName":"Vivaldi","category":"Baroque"},
                        {"id":"8","firstName":"Ludwig van","lastName":"Beethoven","category":"Classical"},
                        {"id":"9","firstName":"Johannes","lastName":"Brahms","category":"Classical"},
                        {"id":"10","firstName":"Francesco","lastName":"Cavalli","category":"Classical"},
                        {"id":"11","firstName":"Fryderyk Franciszek","lastName":"Chopin","category":"Classical"},
                        {"id":"12","firstName":"Antonin","lastName":"Dvorak","category":"Classical"},
                        {"id":"13","firstName":"Franz Joseph","lastName":"Haydn","category":"Classical"},
                        {"id":"14","firstName":"Gustav","lastName":"Mahler","category":"Classical"},
                        {"id":"15","firstName":"Wolfgang Amadeus","lastName":"Mozart","category":"Classical"},
                        {"id":"16","firstName":"Johann","lastName":"Pachelbel","category":"Classical"},
                        {"id":"17","firstName":"Gioachino","lastName":"Rossini","category":"Classical"},
                        {"id":"18","firstName":"Dmitry","lastName":"Shostakovich","category":"Classical"},
                        {"id":"19","firstName":"Richard","lastName":"Wagner","category":"Classical"},
                        {"id":"20","firstName":"Louis-Hector","lastName":"Berlioz","category":"Romantic"},
                        {"id":"21","firstName":"Georges","lastName":"Bizet","category":"Romantic"},
                        {"id":"22","firstName":"Cesar","lastName":"Cui","category":"Romantic"},
                        {"id":"23","firstName":"Claude","lastName":"Debussy","category":"Romantic"},
                        {"id":"24","firstName":"Edward","lastName":"Elgar","category":"Romantic"},
                        {"id":"25","firstName":"Gabriel","lastName":"Faure","category":"Romantic"},
                        {"id":"26","firstName":"Cesar","lastName":"Franck","category":"Romantic"},
                        {"id":"27","firstName":"Edvard","lastName":"Grieg","category":"Romantic"},
                        {"id":"28","firstName":"Nikolay","lastName":"Rimsky-Korsakov","category":"Romantic"},
                        {"id":"29","firstName":"Franz Joseph","lastName":"Liszt","category":"Romantic"},
                        {"id":"30","firstName":"Felix","lastName":"Mendelssohn","category":"Romantic"},
                        {"id":"31","firstName":"Giacomo","lastName":"Puccini","category":"Romantic"},
                        {"id":"32","firstName":"Sergei","lastName":"Rachmaninoff","category":"Romantic"},
                        {"id":"33","firstName":"Camille","lastName":"Saint-Saens","category":"Romantic"},
                        {"id":"34","firstName":"Franz","lastName":"Schubert","category":"Romantic"},
                        {"id":"35","firstName":"Robert","lastName":"Schumann","category":"Romantic"},
                        {"id":"36","firstName":"Jean","lastName":"Sibelius","category":"Romantic"},
                        {"id":"37","firstName":"Bedrich","lastName":"Smetana","category":"Romantic"},
                        {"id":"38","firstName":"Richard","lastName":"Strauss","category":"Romantic"},
                        {"id":"39","firstName":"Pyotr Il'yich","lastName":"Tchaikovsky","category":"Romantic"},
                        {"id":"40","firstName":"Guiseppe","lastName":"Verdi","category":"Romantic"},
                        {"id":"41","firstName":"Bela","lastName":"Bartok","category":"Post-Romantic"},
                        {"id":"42","firstName":"Leonard","lastName":"Bernstein","category":"Post-Romantic"},
                        {"id":"43","firstName":"Benjamin","lastName":"Britten","category":"Post-Romantic"},
                        {"id":"44","firstName":"John","lastName":"Cage","category":"Post-Romantic"},
                        {"id":"45","firstName":"Aaron","lastName":"Copland","category":"Post-Romantic"},
                        {"id":"46","firstName":"George","lastName":"Gershwin","category":"Post-Romantic"},
                        {"id":"47","firstName":"Sergey","lastName":"Prokofiev","category":"Post-Romantic"},
                        {"id":"48","firstName":"Maurice","lastName":"Ravel","category":"Post-Romantic"},
                        {"id":"49","firstName":"Igor","lastName":"Stravinsky","category":"Post-Romantic"},
                        {"id":"50","firstName":"Carl","lastName":"Orff","category":"Post-Romantic"}
                    ]
                };
                var EpochSpecification = dojo.declare(
                schkovich.CompositeSpecification, {
                    constructor: function(epoch) {
                        this.IsSatisfiedBy = function(entity) {
                            return entity.category
                                && (entity.category === epoch);
                        }
                    }
                });
                var FirstLetterSpecification = dojo.declare(
                schkovich.CompositeSpecification, {
                    constructor: function(firstLetter) {
                        this.IsSatisfiedBy = function(entity) {
                            return entity.firstName
                                && (entity.firstName.indexOf(firstLetter) === 0);
                        }
                    }
                });
                var filterComposers = function(specification) {
                    var filtered = dojo.filter(composersData.items, function(entity){
                        return specification.IsSatisfiedBy(entity);
                    });
                    composersData.items = filtered;
                    return composersData;
                }
                var layout = [
                    {
                        name: "First Name",
                        field: "firstName",
                        width: "40%"
                    },

                    {
                        name: "Last Name",
                        field: "lastName",
                        width: "40%"
                    },
                    {
                        name: "Epoque",
                        field: "category",
                        width: "20%"
                    }
                ];
                var spec = new EpochSpecification("Romantic").
                    Or(new EpochSpecification("Post-Romantic")).
                    And(
                        new FirstLetterSpecification("G")
                        .Or(new FirstLetterSpecification("C"))
                    );
                var store = new dojo.data.ItemFileReadStore({
                    data: filterComposers(spec)
                });
                var grid = new dojox.grid.DataGrid({
                    store: store,
                    structure: layout,
                    clientSort: true,
                    rowSelector: '20px',
                    query: {firstName: "*"}
                }, dojo.doc.createElement("div"));
                dojo.place(grid.domNode, dojo.byId("gridPlaceholder"), "first");
                grid.startup();
            });
        </script>
    </body>
</html>

In reality I been using the Specification Pattern to handle highly complex forms having predefined sequence or entering data and numerous validation conditions. In short, each change of input data will trigger a loop over the form elements making sure that each element meets specification.

I am also considering creating data store based on the Specification Pattern: Instead passing complex queries a Specification will be passed.

Sunday, January 9, 2011

Goodbye to VMD

If you are running Linux 64 bit don't even consider using NetBenas Visual Mobile Designer (VMD). Actually do not consider developing mobile applications on any 64 bit platform (Linux, Mac or Windows).

The newer feature-rich Java ME SDK 3.0 is available only for Windows XP and Vista while the old Sun Java Wireless Toolkit 2.5.2 is available only for Linux 32 bit platforms.

In short, although you will see on the NetBeans download page that Java ME is supported technology for Linux 64 bit platform that is not true. One can create project but it is not possible to compile, run or deploy it.

You can try to install JDK 32 bit on Linux 64 bit but eventually you will run into problems.

у вези са: J2ME-er: HelloWorld using NetBeans' Visual Mobile Designer (VMD) (прикажи на Google Sidewiki-ју)

Monday, December 27, 2010

Converting PHP_CodeSniffer XML report into HTML page (XSLT)

PHP CodeSniffer XML report will look similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<phpcs version="1.2.2">
 <file name="/home/myhome/NetBeansProjects/LazyLoad/Libs/LazyLoad/Repositories/NestedSetNode.php" errors="7" warnings="1">
  <error line="39" column="11" source="Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase">Constants must be uppercase; expected LAZYLOAD but found LazyLoad</error>
  <error line="39" column="20" source="Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase">Constants must be uppercase; expected REPOSITORIES but found Repositories</error>
  <error line="58" column="1" source="PEAR.Commenting.ClassComment">Missing @link tag in class comment</error>
  <error line="69" column="13" source="PEAR.NamingConventions.ValidVariableName">Private member variable &quot;set&quot; must be prefixed with an underscore</error>
  <error line="75" column="13" source="PEAR.NamingConventions.ValidVariableName">Private member variable &quot;lft&quot; must be prefixed with an underscore</error>
  <error line="81" column="13" source="PEAR.NamingConventions.ValidVariableName">Private member variable &quot;rgt&quot; must be prefixed with an underscore</error>
  <warning line="148" column="9" source="PEAR.ControlStructures.InlineControlStructure">Inline control structures are discouraged</warning>
 </file>
</phpcs>
To transform PHP CodeSniffer XML report into human readable format XSLT stylesheet needs to be attached:
<?xml version="1.0" encoding="UTF-8"?>
<!-- add line -->
<?xml-stylesheet type="text/xsl" href="phpcs.xsl"?>
<!-- rest of PHP CodeSniffer XML report -->
XSLT stylesheet:
<?xml version="1.0" encoding="UTF-8"?>

<!--
    Document   : phpcs.xsl
    Created on : December 27, 2010, 1:42 PM
    Author     : schkovich
    Description:
        Transformation PHP_CodeSniffer xml report into human readable format.
-->

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="html"  encoding="UTF-8"/>

    <!-- TODO customize transformation rules
         syntax recommendation http://www.w3.org/TR/xslt
    -->
    <xsl:template match="/">
        <html>
            <head>
                <title>phpcs.xsl</title>
                <link href="./phpcs.css" rel="stylesheet" type="text/css" />
            </head>
            <body>
                <table>
                    <thead>
                        <tr>
                            <th class="file">Name</th>
                            <th class="notes">Errors</th>
                            <th class="notes">Warnings</th>
                        </tr>
                    </thead>
                    <tbody>
                        <xsl:for-each select="phpcs/file">
                            <tr>
                                <td>
                                    <xsl:value-of select="@name" />
                                </td>
                                <td>
                                    <xsl:value-of select="@errors" />
                                </td>
                                <td>
                                    <xsl:value-of select="@warnings" />
                                </td>
                            </tr>
                            <tr>
                                <td colspan="3">
                                    <xsl:for-each select="error">
                                        <span class="error">Error: </span>
                                        <xsl:value-of select="self::node()"/>
                                        <br />
                                        <b>Line:</b>
                                        <xsl:value-of select="@line" />
                                        <br />
                                        <b>Column:</b>
                                        <xsl:value-of select="@column" />
                                        <br />
                                        <b>Source:</b>
                                        <xsl:value-of select="@source" />
                                        <hr />
                                    </xsl:for-each>
                                    <xsl:for-each select="warning">
                                        <span class="warning">Warning: </span>
                                        <xsl:value-of select="self::node()"/>
                                        <br />
                                        <b>Line:</b>
                                        <xsl:value-of select="@line" />
                                        <br />
                                        <b>Column:</b>
                                        <xsl:value-of select="@column" />
                                        <br />
                                        <b>Source:</b>
                                        <xsl:value-of select="@source" />
                                        <hr />
                                    </xsl:for-each>
                                </td>
                            </tr>
                        </xsl:for-each>
                    </tbody>
                </table>
            </body>
        </html>
    </xsl:template>

</xsl:stylesheet>
CSS
/*
    Document   : phpcs
    Created on : Dec 27, 2010, 1:34:02 PM
    Author     : schkovich
    Description:
        This stylesheet is designed to style phpcs XSLT stylessheet
*/

/*
   TODO customize this sample style
   Syntax recommendation http://www.w3.org/TR/REC-CSS2/
*/
table {
    width: 100%
}
th {
    text-align: left
}
th.file {
    width: 80%;
    color: green
}
th.notes {
    width: 10%;
    color: blue
}
span {
    font-weight: bold
}
span.error {
    color: red
}
span.warning {
    color: orange
}
Output will look like this:
To generate PHP CodeSniffer reports that will always have attached external XSLT stylesheet one PHP CodeSniffer file needs to be patched and one needs to be added.
New code generator CodeSniffer/Reports.Xls.php needs to be added:
<?php
/**
 * Xsl report for PHP_CodeSniffer.
 *
 * PHP version 5
 *
 * @category  PHP
 * @package   PHP_CodeSniffer
 * @author    Gabriele Santini <gsantini@sqli.com>
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @author    Goran Miskovic <schkovich@gmail.com>
 * @copyright 2009 SQLI <www.sqli.com>
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
 * @version   CVS: $Id: IsCamelCapsTest.php 240585 2007-08-02 00:05:40Z squiz $
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 */

/**
 * Xml report for PHP_CodeSniffer.
 *
 * PHP version 5
 *
 * @category  PHP
 * @package   PHP_CodeSniffer
 * @author    Gabriele Santini <gsantini@sqli.com>
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @author    Goran Miskovic <schkovich@gmail.com>
 * @copyright 2009 SQLI <www.sqli.com>
 * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
 * @version   Release: 1.2.2
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 */
class PHP_CodeSniffer_Reports_Xsl implements PHP_CodeSniffer_Report
{


    /**
     * Prints all violations for processed files, in a proprietary XML format.
     *
     * Errors and warnings are displayed together, grouped by file.
     * 
     * External XSLT stylesheet phpcs.xsl will be attached
     *
     * @param array   $report       Prepared report.
     * @param boolean $showWarnings Show warnings?
     * @param boolean $showSources  Show sources?
     * @param int     $width        Maximum allowed lne width.
     * 
     * @return string 
     */
    public function generate(
        $report,
        $showWarnings=true,
        $showSources=false,
        $width=80
    ) {
        echo '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
        echo '<?xml-stylesheet type="text/xsl" href="phpcs.xsl"?>'.PHP_EOL;
        echo '<phpcs version="1.2.2">'.PHP_EOL;

        $errorsShown = 0;

        foreach ($report['files'] as $filename => $file) {
            if (empty($file['messages']) === true) {
                continue;
            }

            echo ' <file name="'.$filename.'" errors="'.$file['errors'].'" warnings="'.$file['warnings'].'">'.PHP_EOL;

            foreach ($file['messages'] as $line => $lineErrors) {
                foreach ($lineErrors as $column => $colErrors) {
                    foreach ($colErrors as $error) {
                        $error['type'] = strtolower($error['type']);
                        echo '  <'.$error['type'].' line="'.$line.'" column="'.$column.'" source="'.$error['source'].'">';
                        echo htmlspecialchars($error['message']).'</'.$error['type'].'>'.PHP_EOL;
                        $errorsShown++;
                    }
                }
            }//end foreach

            echo ' </file>'.PHP_EOL;
        }//end foreach

        echo '</phpcs>'.PHP_EOL;

        return $errorsShown;

    }//end generate()


}//end class

?>
CodeSniffer/CLI.php needs to be patched. Find variable $validReports on line 262 and add 'xsl' as the last array element:
$validReports     = array(
                                     'full',
                                     'xml',
                                     'checkstyle',
                                     'csv',
                                     'emacs',
                                     'source',
                                     'summary',
                                     'svnblame',
                                     'xsl',
                                    );
Finally run from the command line:
phpcs --report=xsl --report-file=${HOME}/NetBeansProjects/LazyLoad/public_html/LazyLoad.xml NetBeansProjects/LazyLoad/Libs