Libellés

dimanche 14 août 2011

Javascript et les objets : héritage, prototype, constructor, ...


Javascript n'est pas un langage orienté objet, mais à l'aide des
prototypes nous pouvons quand même nous en sortir.


### Création d'un objet

Le Js est un langage fonctionnel, ce qui signifie que les fonctions
sont au centre du langage. Et donc, pour créer l’équivalent d'une
classe en js, nous allons commencer par créer son constructeur : une fonction.

   var myClass = function(){
     this.name = 'Romain';
   }


Puis ensuite nous allons instancier cet objet :

   var o = new myClass();
   console.log(o.name);


L'instanciation va se dérouler en plusieurs étapes. Tout d'abord un
objet o tout vide va être créé. Ensuite le constructeur myClass va
être appelé sur cette objet. Le this.name va permettre de créer
l'attribut name dynamiquement.


### Le prototype

Le prototype est le dénominateur commun entre toute les instances d'une même classe.

Si j'ajoute un attribut(ou une méthode) sur le prototype de myClass, alors cet attribut (ou méthode) sera present sur toute les instances de myClass.

   myClass.prototype.age = 25;
  
   console.log(o.age);



### Lien entre le prototype et le constructor.

Comme nous l'avons vu tout à l'heure myClass est une fonction qui va permettre de créer l'objet. C'est le constructeur.

  console.log(typeof myClass) //function


A partir de cette fonction nous pouvons accéder au prototype :

  console.log(myClass.prototype) //{ age: 25 }



Et à partir du prototype nous pouvons retrouver le constructeur. Ce qui signifie qu'à partir de n'importe quel objet, nous pouvons retrouver le constructeur car comme nous l'avons vu précédemment toutes les methodes et attributs d'un prototype sont accessible dans les instances.

   console.log( myClass.prototype.constructor === myClass)
   console.log( o.constructor === myClass)



### L'héritage en javascript

Comme chaque instance hérite de son prototype, il suffit de chainer les prototypes pour faire de l'héritage.

Voici un exemple complet :

    /*
     * On créé le constructeur de l'objet A.
     * Ce constructeur va ajouter automatiquement un attribut name à l'objet.
     */
    var A = function(){
      this.name="Romain";  
    };

    A.prototype.sayHello = function(){
       return "Hello "+this.name;  
    };

    /* On fait de même pour l'objet B */
    var B = function(){
      this.age= 25;  
    };

    /* Maintenant on dit que B hérite de l'objet A */
    B.prototype = new A();

    /* Pour ne pas perdre la référence vers constructeur de B */
    B.prototype.constructor = B;


    /* On vérifie que ça marche */
    var o = new B();
    console.log(o.name);             //Romain
    console.log(o.age);              //25
    console.log(o.sayHello());       //Hello Romain
    console.log(o instanceof B);     // true
    console.log(o instanceof A);     //true
    console.log(o instanceof Object);//true

    /* Et pour le fun, on vérifie que les constructeurs sont logiques */
    console.log(B.prototype.constructor === B);  //true
    console.log(A.prototype.constructor === A);  //true


    /* On peut même recréer un objet à partir d'un autre */
    var p = new o.constructor();
    console.log(p.name);  //Romain
    console.log(p.age);   //25

samedi 13 août 2011

Jaxrs client avec Jersey

Ce n'est souvent pas évident de trouver un client Jaxrs. Jersey vous en propose un, et voici comment l'utiliser.  Par défaut j'ai rajouté les logs des requêtes HTTP :


  @Grab('com.sun.jersey:jersey-client:1.8')
  @Grab('com.sun.jersey:jersey-core:1.8')
  @Grab('com.sun.jersey.contribs:jersey-apache-client4:1.8')

  import com.sun.jersey.api.client.*;
  import com.sun.jersey.api.client.filter.LoggingFilter;
  import javax.ws.rs.core.*;
  import com.sun.jersey.client.apache4.ApacheHttpClient4;

  Client client = ApacheHttpClient4.create();
  client.addFilter(new LoggingFilter());
  def resource = client.resource("http://geek-du-soir.blogspot.com").path("/feeds/posts/default")
  resource.accept("application/xml+atom").get(String.class);

jeudi 11 août 2011

Logguer les requettes HTTP de Jersey Client

Pour logguer les requêtes HTTP éffectuées par Jersey Client il suffit de rajouter :

client.addFilter(new LoggingFilter());


Voici un exemple complet :


@Grab('com.sun.jersey:jersey-client:1.8')
@Grab('com.sun.jersey:jersey-core:1.8')
@Grab('com.sun.jersey.contribs:jersey-apache-client4:1.8')

import com.sun.jersey.api.client.*;
import com.sun.jersey.api.client.filter.LoggingFilter;
import javax.ws.rs.core.*;


Client client = create();
client.addFilter(new LoggingFilter());
def resource = client.resource("http://geek-du-soir.blogspot.com").path("/feeds/posts/default")

resource.accept(MediaType.APPLICATION_XML).get(String.class);



import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import com.sun.jersey.client.apache4.ApacheHttpClient4;
import com.sun.jersey.client.apache4.config.DefaultApacheHttpClient4Config;

ApacheHttpClient4 create() {
DefaultApacheHttpClient4Config config = new DefaultApacheHttpClient4Config();
ApacheHttpClient4 jerseyClient = ApacheHttpClient4.create(config);
HttpClient httpClient = jerseyClient.getClientHandler().getHttpClient();
SSLSocketFactory sf = (SSLSocketFactory) httpClient.getConnectionManager().getSchemeRegistry().getScheme("https").getSchemeSocketFactory();
sf.setHostnameVerifier(new AllowAllHostnameVerifier());
return jerseyClient;
}

mercredi 10 août 2011

getter et setter avec nodeJs : EcmaScript 5


NodeJs permet d'écrire du code en ECMA Script 5, nous pouvons donc profiter des getter et setter :


/*jslint es5: true */

function MyClass() {
    var name;

    return {
        get name() {
            return name;
        },
        set name(value) {
            name = 'Mr ' + value;
        }
    };
}

var obj = new MyClass();

obj.name = "Romain";   //call the setter
console.log(obj.name); //call the getter

/* print 'Mr Romain' */

mardi 9 août 2011

Commencer nodeJs avec npm, cloud9 et vows sur Archlinux

J'ai la chance de pouvoir tester nodeJs actuellement, je vais donc essayer de vous en faire profiter via mon blog.

Si vous êtes sous windows passez votre chemin. Désolé :-)

Pour les utilisateurs de Linux, je vais surtout vous expliquer comment j'ai installé mon environnement de développement sous Archlinux, mais il est également assez facile de le faire avec Ubuntu (via les ppa)


### Installer nodeJs

NodeJs est le JsDK basé sur le moteur javascript V8 de google, qui vous permettra de coder vos applications en JavaScript.

Contrairement à Java, nodeJs démarre très vite et ne prend pas beaucoup de RAM. C'est très agréable !!!

Le paquet nodejs se trouve dans community, pour l'installer il vous suffit donc de tapper :

pacman -S nodejs

### Installer npm

NPM comme node package manager, correspond au maven java, au gem ruby, ... c'est le gestionnaire de paquet.

Npm se trouve sur AUR, si vous avez yaourt vous pouvez l'installer comme ceci :

yaourt -S nodejs-npm


### Installer cloud9

Cloud9 est un IDE node réalisé en node. C'est un exemple vivant de ce que l'on peut faire avec nodeJs. Je vous conseil de l'installer rien que pour ça.

Le paquet AUR de cloud9 est moins bien conçu que les autres (mais cloud9 n'est simplement pas évident à installer)

yaourt -S cloud9

Puis éditer le PKGBUILD pour appliquer ce patch :

git clone $gitroot $gitname
cd $gitname
- git checkout $gittag
+ #git checkout $gittag
git submodule update --init --recursive


For x86 users, the build fails. You have to remove the 64 bit files :

package() {
+ find -name *linux64* | while read file; do
+ rm $file
+ done
install -d $pkgdir/opt/cloud9


### Installer vows

Vows est la librairie de test nodeJs. Il permet d'écrire des specifications de son code au format bdd. Pour plus d'info : http://vowsjs.org/

Pour l'installer sur votre système, il faut maintenant se reposer sur npm. C'est npm qui va se charger d'installer vows dans /usr

sudo npm install -g vows

sudo pour lui donner les accès root
-g pour qu'il l'installe dans /usr/bin/vows


### Démarrer cloud9

Cloud9 est un serveur web, vous accéderez à l'IDE via votre navigateur web, cool n'est-ce pas :) Et en plus c'est plus léger qu'un IDE lourd !

Bien évidemment Cloud9 n'est actuellement pas l'équivalent de Netbeans mais il propose déjà des corrections de syntaxe, la coloration syntaxique, la mise en forme automatique du code, une console et un debugger (qui ne marche actuellement pas chez moi).

Le meilleurs IDE Js est à mon avis IntelliJ mais il est payant. Sinon NetBeans fonctionne plutôt bien.

Pour lancer cloud9, il faut se placer dans le répertoire de son projet js et lancer la commande :

$ cd myProject
$ cloud9 &
...
Point your browser to http://127.0.0.1:3000


### Premier projet js

Créons la structure du projet :

$ touch index.js #main file
$ mkdir lib/ #sources
$ mkdir test/ #tests


Créons notre premier module :

$ cat lib/Playground.js

//Je déclare un classe, qui sera instanciée en tant qu'objet plus tard
function myClass(){
this.name= "Romain";
this.age=25;
this.getAge= function(){ return this.age; };
this.setAge= function(age){ this.age=age; };
}

//Puis je l'exporte pour pouvoir l'utiliser dans mes tests et dans mon projet
exports.myClass = myClass;



Et le test associé :

var vows = require('vows'),
assert = require('assert'),
playground = require('../lib/Playground');

var myClass = playground.myClass;

vows.describe('play with prototype').addBatch({
'given an object': { //je commence ma description au format BDD
topic: new myClass(), //topic sera l'objet de mon test

'when age is changed': {
topic: function (obj) { //obj === topic
obj.setAge(20);
return obj; //et je redéfinie topic pour qu'il reste === obj
},

'then I get the new age': function (obj) {
assert.equal(21, obj.getAge());
}
}
}
}).exportTo(module); //obligatoire pour utiliser vows comme cli.


Et maintenant si on run vows (soit via cloud9, soit via le cli) :

$ vows test/*.js

given an object when age is changed
✗ then I get the new age
» expected 20,
got 21 (==) // TestPlayGround.js:18

✗ Broken » 1 broken (0.015s)

Sympa le format de sortie :)

jeudi 21 juillet 2011

Ecrire un DSL en Groovy

Récemment j'ai écrit un DSL en Groovy pour WRO4J, et je vais vous expliquer comment y arriver soi même.

Voici à quoi ressemble au final le DSL :

groups {
g1 {
js "/static/app.js"
css "/static/app.css"
}
"an-other-group" {
js("classpath:com/application/static/app.js")
css(minimize: false, "http://www.site.com/static/app.css")
}
}

https://github.com/Filirom1/wro4j/blob/master/wro4j-extensions/src/test/resources/ro/isdc/wro/extensions/model/factory/Wro.groovy

Si l'on analyse un plus le DSL, on retrouve :

- une closure groups englobant l'ensemble du dsl
- deux closures g1 et an-other-group ayant des noms personnalisées
- deux methodes js et css, pouvant prendre 1 ou deux arguments : js(Map params = [:], String uri)


Maintenant pour construire ce DSL nous allons partir de la fin, les méthodes js et css.
Voici le code source, regarder la signature des méthodes :

class ResourceDelegate {
List resources = new ArrayList()

void css(Map params = [:], String name) {
def resource = Resource.create(name, ResourceType.CSS)
if (params.get("minimize") == false) resource.minimize = false
resources.add(resource)
}

void js(Map params = [:], String name) {
def resource = Resource.create(name, ResourceType.JS)
if (params.get("minimize") == false) resource.minimize = false
resources.add(resource)
}
}

Le premier paramètre params est facultatif. Nous pouvons donc utiliser ces méthodes comme ceci :

def resourceDelegate = new ResourceDelegate()

//avec params
resourceDelegate.js([minimize: true, key: value], "js/script.js")
resourceDelegate.js(minimize: true, key: value, "js/script.js")

//et sans params
resourceDelegate.js([:], "js/script.js")
resourceDelegate.js("js/script.js")
resourceDelegate.js "js/script.js"


Maintenant il nous faut intégrer ces méthodes dans une closure pour pouvoir écrire un DSL comme celui là :

myGroup{
js "script.js"
js(minimized: true, "script2.js")
}


Je défini ici une méthode myGroup prenant comme seul paramètre une closure et qui exécute cette closure.

class GroupDelegate {
def myGroup(Closure cl){
cl()
}
}

Je peux donc appeler cette méthode de différentes façons :

myGroup({
println "inside the closure"
})
myGroup {
println "inside the closure"
}

Je vais choisir la deuxième façon qui est plus joli pour un DSL.

Je modifie cette closure pour déléguer tout les appels se passant à l'interieur de cette closure vers une instance de ResourceDelegate (défini dans la première partie)

class GroupDelegate {
List groups = new ArrayList()

def myGroup(Closure cl){
cl.delegate = new ResourceDelegate()
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
groups.add(new Group(name: 'myGroup', resources: cl.resources))
}
}

Et maintenant je peux écrire :

@Test
public void testGroupDelegate() {
//setup:
def groupDelegate = new GroupDelegate()

//when:
groupDelegate.myGroup {
js("/js/script.js")
css("/css/style.css")
}

//then:
assert 1 == groupDelegate.groups.size()
assert "myGroup" == groupDelegate.groups[0].name
assert 2 == groupDelegate.groups[0].resources.size()
}


Notre DSL commence à prendre forme, mais nous ne pouvons créer qu'un seul groupe. L'idée maintenant serait de pouvoir écrire plusieurs groupes comme ci dessous :

groupDelegate.g1 {
js("/js/script.js")
css("/css/style.css")
}
groupDelegate.'second-group' {
js("/js/script.js")
css("/css/style.css")
}


Rien de plus simple en groovy, il suffit d'utiliser la méthode def methodMissing(String name, args).
La méthode methodMissing est appelée quand groovy ne trouve aucune méthode correspondant. La variable name nous donne le nom de la méthode appelée et args la liste des arguments passés à la méthode.

Voici cette fois la classe GroupDelegate utilisant methodMissing :

class GroupDelegate {
List groups = new ArrayList()

def methodMissing(String name, args) {
def cl = args[0]
cl.delegate = new ResourceDelegate()
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
groups.add(new Group(name: name, resources: cl.resources))
}
}


On approche de la fin, maintenant il nous faut intégrer les différents groupes à l'intérieur d'une closure comme ceci :

wroModelDelegate.groups {
g1 {
js("/js/script.js")
css("/css/style.css")
}
'second-group' {
js("/js/script2.js")
}
}

Comme nous avons déjà réalisé cette tâche précédemment, je vous donne directement la solution (il faut déléguer les appels se passant à l’intérieur de la closure vers une instance de GroupDelegate) :

class WroModelDelegate {
WroModel wroModel = new WroModel()

void groups(Closure cl) {
cl.delegate = new GroupDelegate()
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
wroModel = new WroModel(groups: (Collection) cl.getProperty("groups"))
}

}

Courage on approche de la fin. Maintenant on aimerait pouvoir écrire notre DSL dans un fichier dsl.groovy, parser ce fichier et récupérer la valeur.
Pour cela nous allons travailler avec la classe Script de groovy.

def dsl = """
groups {
g1 {
js("/js/script.js")
css("/css/style.css")
}
g2 {
js("/js/script2.js")
}
}
"""

Script dslScript = new GroovyShell().parse(dsl)
dslScript.metaClass.mixin(WroModelDelegate)
dslScript.run()

Ce script (dslScript) contient une liste d'instruction dont la première est d'appeler la methode groups(Closure). Sauf que cette méthode n'est pas connu naturellement dans dslScript.
C'est pour cela que nous faisons appel à dslScript.metaClass.mixin(WroModelDelegate).
Nous mixons le script avec WroModelDelegate, ce qui correspond à ajouter toute les méthodes et les attributs de WroModelDelegate dans le dslScript.

Maintenant il suffit d'executer le script et c'est gagné :D
dslScript.run()



L'ensemble du code source se trouve ici : https://github.com/Filirom1/wro4j/blob/master/wro4j-extensions/src/main/groovy/ro/isdc/wro/extensions/model/factory/GroovyWroModelParser.groovy

Et les tests là :
https://github.com/Filirom1/wro4j/blob/master/wro4j-extensions/src/test/groovy/ro/isdc/wro/extensions/model/factory/TestGroovyWroModelParser.groovy


Une version testable et modifiable en ligne est également disponible ici :
http://groovyconsole.appspot.com/script/516002

Amusez vous bien !!!

PS : je me suis fortement inspiré de http://groovy.dzone.com/news/groovy-dsl-scratch-2-hours

GroovyClassLoader charger et exécuter un Script

Lorsque l'on travaille avec Groovy il est assez facile de charger une classe avec GroovyClassLoader#loadClass

Mais comment faire pour charger et exécuter un script comme celui ci :

[romain@dahu tmp]$ cat MyScript.groovy
println ">>>>>>>>>>>>>>>> It works"
println 10 + 2


En fait c'est assez simple, il suffit de charger la classe, de l'instancier puis de la caster en Script.

[romain@dahu tmp]$ ls
MyScript.groovy Runner.groovy


[romain@dahu tmp]$ cat Runner.groovy
def clazz = this.getClass().getClassLoader().loadClass("MyScript")
def script = (Script) clazz.newInstance()
script.run()


Et ça fonctionne :

[romain@dahu tmp]$ groovy Runner.groovy
>>>>>>>>>>>>>>>> It works
12