Invalidando o cache do Docker

O Docker é uma tecnologia fantástica. Através de recursos do kernel do Linux, a ferramenta permite executar o código da sua aplicação em “máquinas virtuais” (na verdade, containers) muito leves.

Uma coisa chata é que o Docker funciona nativamente apenas em distribuições Linux. Pra usar no Mac OS X ou no Windows, você precisa de uma máquina virtual VirtualBox.

Receita

Com o Docker, você pode definir uma “receita” em um arquivo chamado Dockerfile. Através de comandos pré-definidos, você fornece um passo-a-passo que é usado para criar templates de máquinas, conhecidos como imagens. As imagens são usadas para criar os containers que realmente executam aplicações. Um exemplo de Dockerfile seria:

FROM ubuntu:trusty

RUN apt-get install -y --no-install-recommends \
	curl \
	git

#instalando nodejs 0.12.4 (com npm 2.10.1 incluso)
RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" \
	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 \
	&& rm "node-v0.12.4-linux-x64.tar.gz" 

RUN npm install -g alexandreaquiles/marechal-random

CMD marechal-random

O comando FROM permite o uso de uma imagem pré-definida. Já o RUN permite que algo seja executado na nova imagem que está sendo criada. O CMD define o comando que deve ser executado por padrão nos containers criados a partir dessa imagem.

No Dockerfile acima:

  • criamos uma imagem baseada no Ubuntu 14.04 (trusty);
  • instalamos as ferramentas curl e git;
  • instalamos o Node JS;
  • através do npm, instalamos o módulo Node JS alexandreaquiles/marechal-random globalmente, permitindo que seja executado em linha de comando. O código é obtido a partir do repositório github.com/alexandreaquiles/marechal-random (e não do npm registry);
  • configuramos a execução do comando marechal-random por padrão, que invoca nosso módulo Node JS

Executando

O módulo Node JS marechal-random é uma ferramenta bem simples de linha de comando que, quando executada, mostra um número randômico maior que 0 e menor que 1. O código do arquivo index.js é o seguinte:

#!/usr/bin/env node
console.log(Math.random());

Por meio do comando docker build, podemos criar uma imagem chamada maquina a partir do Dockerfile:

docker build -t maquina pasta-do-Dockerfile/

Será mostrado algo como:

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Running in 512f2fb0af33
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
  git-man libasn1-8-heimdal libcurl3 libcurl3-gnutls liberror-perl ...
Recommended packages:
  patch rsync ssh-client ca-certificates krb5-locales libsasl2-modules
The following NEW packages will be installed:
  curl git git-man libasn1-8-heimdal libcurl3 libcurl3-gnutls liberror-perl ...
0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.
Need to get 5367 kB of archives.
After this operation, 29.3 MB of additional disk space will be used.
...
 ---> 78b2ceb410cc
Removing intermediate container 512f2fb0af33
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Running in 8dc493ab0aeb
 ---> 41f993547202
Removing intermediate container 8dc493ab0aeb
Step 4 : RUN npm install -g alexandreaquiles/marechal-random
 ---> Running in e366e4914731
/usr/local/bin/marechal-random -> /usr/local/lib/node_modules/marechal-random/index.js
marechal-random@1.0.0 /usr/local/lib/node_modules/marechal-random
 ---> 748021adbd43
Removing intermediate container e366e4914731
Step 5 : CMD marechal-random
 ---> Running in 99d8f0df0e79
 ---> 9974d8f6a06b
Removing intermediate container 99d8f0df0e79
Successfully built 9974d8f6a06b

Então, através do docker run, podemos executar o comando padrão:

docker run --rm  maquina

É criado um container a partir da imagem maquina. A opção --rm faz com que esse container seja removido logo após o término da execução, economizando espaço em disco.

O resultado será algum número randômico:

0.6202559822704643

Se executarmos novamente, teremos outros resultado:

0.13229440338909626

Alterações

Vamos alterar esse módulo, para que seja impressa a mensagem “Random” antes do número:

#!/usr/bin/env node
console.log('Random: '+Math.random());

Depois de feito commit e push para o repositório do GitHub, vamos regerar a imagem para que o módulo marechal-random seja atualizado:

docker build -t maquina pasta-do-Dockerfile/

O resultado seria:

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> 78b2ceb410cc
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> 41f993547202
Step 4 : RUN npm install -g alexandreaquiles/marechal-random
 ---> Using cache
 ---> 748021adbd43
Step 5 : CMD marechal-random
 ---> Using cache
 ---> 9974d8f6a06b
Successfully built 9974d8f6a06b

Perceba que todos os comandos tem a mensagem Using cache, incluindo a instalação do módulo marechal-random (Step 4).

Vamos, então, executar novamente nosso comando:

docker run --rm  maquina

Teremos:

0.2565556331537664

A mensagem “Random” não apareceu na frente do número!

A raiz do problema está na criação da imagem maquina a partir do Dockerfile: se exatamente o mesmo comando for executado, é utilizado o cache pré-existente. E o que é comparado é o texto do comando! Como RUN npm install -g alexandreaquiles/marechal-random já tinha sido executado, foi utilizado o cache 748021adbd43.

Invalidação de cache

O que fazer então para que, toda vez que mudarmos o código de marechal-random no GitHub, seja criada uma nova imagem?

Precisamos fazer com que o cache seja invalidado.

O comando COPY pode ser usado no Dockerfile para copiar um arquivo para dentro da imagem que está sendo criada. Se o conteúdo do arquivo ou algum metadado for modificado, o cache é invalidado no COPY e nos comandos posteriores.

Vamos passar a instalação do módulo marechal-random para um arquivo chamado instala.sh:

#!/bin/bash
npm install -g alexandreaquiles/marechal-random

É importante que o arquivo instala.sh tenha permissão de execução.

Depois disso, vamos modificar o Dockerfile para que o arquivo instala.sh seja copiado para a imagem:

FROM ubuntu:trusty

RUN apt-get install -y --no-install-recommends \
	curl \
	git

#instalando nodejs 0.12.4 (com npm 2.10.1 incluso)
RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" \
	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 \
	&& rm "node-v0.12.4-linux-x64.tar.gz" 

COPY instala.sh /modulos/
RUN /modulos/instala.sh

CMD marechal-random

Devemos regerar a imagem maquina:

docker build -t maquina pasta-do-Dockerfile/

Teremos a seguinte saída:

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> 78b2ceb410cc
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> 41f993547202
Step 4 : COPY instala.sh /modulos/
 ---> d3bbc70bc9ac
Removing intermediate container b19aa7ff986b
Step 5 : RUN /modulos/instala.sh
 ---> Running in 37fc84fbcb65
/usr/local/bin/marechal-random -> /usr/local/lib/node_modules/marechal-random/index.js
marechal-random@1.0.0 /usr/local/lib/node_modules/marechal-random
 ---> 686a674f0e1f
Removing intermediate container 37fc84fbcb65
Step 6 : CMD marechal-random
 ---> Running in 11a58e68a5d6
 ---> cbface0310ed
Removing intermediate container 11a58e68a5d6
Successfully built cbface0310ed

Perceba que, para os três primeiros comandos, foram utilizados os caches. A partir do COPY (Step 4), não foi usado o cache. No RUN (Step 5), foi feita a instalação do módulo marechal-random.

Mas não foi usado o cache desses comandos porque modificamos o conteúdo do Dockerfile. Para testar se a invalidação está funcionando, precisamos regerar a imagem:

docker build -t maquina pasta-do-Dockerfile/

Teremos:

Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> ce97fca9b976
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> bfef268a14e4
Step 4 : COPY instala.sh /modulos/
 ---> Using cache
 ---> d3bbc70bc9ac
Step 5 : RUN /modulos/instala.sh
 ---> Using cache
 ---> 686a674f0e1f
Step 6 : CMD marechal-random
 ---> Using cache
 ---> cbface0310ed
Successfully built cbface0310ed

Ih! Não funcionou! Foi usado o cache para todos os comandos!

Acontece que, para invalidar o cache do COPY, precisamos mudar o conteúdo ou metadados do arquivo instala.sh.

Até a versão 1.8 do Docker, poderíamos modificar o último acesso do arquivo com um touch. Porém, da versão 1.8 em diante, o último acesso e última modificação de arquivo são desconsiderados.

Para mudar o conteúdo do instala.sh sem mudar seu comportamento, podemos adicionar um comentário com a data atual na última linha do arquivo:

echo "#$(date)" >> instala.sh

Então, vamos construir novamente a imagem maquina:

docker build -t maquina pasta-do-Dockerfile/

Veremos como resposta:

Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> ce97fca9b976
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> bfef268a14e4
Step 4 : COPY instala.sh /modulos/
 ---> fdcbc296ca90
Removing intermediate container c8ddba926b63
Step 5 : RUN /modulos/instala.sh
 ---> Running in 84bae6632f68
/usr/local/bin/marechal-random -> /usr/local/lib/node_modules/marechal-random/index.js
marechal-random@1.0.0 /usr/local/lib/node_modules/marechal-random
 ---> ec2b0f560f1c
Removing intermediate container 84bae6632f68
Step 6 : CMD marechal-random
 ---> Running in 54d91a990478
 ---> 9c4aefc0ab6b
Removing intermediate container 54d91a990478
Successfully built 9c4aefc0ab6b

Uma boa prática é colocar os comandos que invalidarão o cache por último no seu Dockerfile.

Criando um jogo em Javascript [3/5] – Carros

Daremos continuidade à implementação em Javascript de uma versão do jogo da galinha do Atari.

No primeiro post, fizemos o pano de fundo do jogo.

Já no segundo post, fizemos o personagem principal, movendo-o a partir das setas do teclado e limitando até onde é possível ir.

Agora, vamos colocar alguns carros, que poderão colidir com o nosso personagem principal.

Carros

Colocaremos, sobre o nosso fundo, três carros:

Carrinho amareloCarrinho azulCarrinho de polícia

Essas imagens podem ser baixadas em:
http://a-dilminha.appspot.com/carrinho-amarelo.png
http://a-dilminha.appspot.com/carrinho-azul.png
http://a-dilminha.appspot.com/carrinho-policia.png

Precisamos inseri-las na página.

Para o nosso personagem principal, fizemos da seguinte maneira:

function desenhaImagem(){
   contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
};

//personagem principal
var imagem = new Image();
imagem.src = "../../dilminha.png";
var x = 320;
var y = 400;
imagem.onload = desenhaImagem;

Para os carros, devemos fazer coisa parecida, alterando os valores do src, do x e do y. Então, teríamos o seguinte código:

//carrinho amarelo
var imagem2 = new Image();
imagem2.src = "../../carrinho-amarelo.png";
var x2 = -10;
var y2 = 300;
imagem2.onload = desenhaImagem;

//carrinho azul
var imagem3 = new Image();
imagem3.src = "../../carrinho-azul.png";
var x3 = 560;
var y3 = 200;
imagem3.onload = desenhaImagem;

//carrinho de polícia
var imagem4 = new Image();
imagem4.src = "../../carrinho-policia.png";
var x4 = 10;
var y4 = 100;
imagem4.onload = desenhaImagem;

Repare nos números para o x e y em cada imagem. Quanto maior o x, mais para direita. Quanto maior o y, mais para baixo. Assim, o carrinho amarelo fica na faixa de baixo, bem à esquerda (até um pouco fora da tela). Já o carro azul é posicionado na faixa do meio e à direita. O carrinho de polícia fica na fixa de cima, à esquerda.

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/carros/10-carrinhos.html

Imagem da Dilminha sobre o fundo

Organizando o código com objetos

Ao testarmos o código acima, vamos perceber que os carrinhos não foram exibidos! Que coisa! Mas por que?

O que acontece é que tentamos reutilizar o código da função desenhaImagem mas deixamos referências fixas às variáveis x, y e imagem, que são referentes ao personagem principal.

Poderíamos deixar o x, o y e a imagem como parâmetros da função desenhaImagem. Mas vamos usar uma abordagem diferente para organizar o código: objetos.

Essas imagens dos personagens de um jogo são comumente chamadas de sprites.

Então, vamos criar um construtor Javascript chamado Sprite que terá todas a estrutura comum entre o personagem principal e cada um dos carros:

function Sprite(caminhoDaImagem, xInicial, yInicial) {
	this.x = xInicial;
	this.y = yInicial;

	this.imagem = new Image();
	this.imagem.src = caminhoDaImagem;

	var that = this;
	this.imagem.onload = function() {
		that.largura = that.imagem.width;
		that.altura = that.imagem.height;
		that.desenhaImagem();
	}

	this.desenhaImagem = function() {
		contexto.drawImage(this.imagem, this.x, this.y, this.largura, this.altura);
	}
}

Como parâmetro desse construtor recebemos o caminho da imagem, o x inicial e o y inicial. Através do this, guardamos o x e y e criamos uma imagem usando o caminho recebido como parâmetro.

No onload, função chamada assim que uma imagem é carregada, não temos acesso ao this. Por isso, utilizamos um truque muito comum: guardamos o this em uma variável chamada that. A variável that fica acessível porque é carregada na definição da função através do mecanismo chamado de closure.

Para criar o personagem principal e os carros, teremos algo como:

var dilminha = new Sprite("../../dilminha.png", 320, 400);

var carrinhoAmarelo = new Sprite("../../carrinho-amarelo.png", -10, 300);

var carrinhoAzul = new Sprite("../../carrinho-azul.png", 560, 200);

var carrinhoPolicia = new Sprite("../../carrinho-policia.png", 10, 100);

Invocamos o construtor Sprite com o new, para criar os objetos do personagem principal e de cada carro. Além disso, demos nomes melhores às variáveis.

Devemos alterar a função que passamos ao setInterval para que sejam usados os objetos que acabamos de criar:

setInterval(function(){
	desenhaFundo();
	dilminha.desenhaImagem();
	carrinhoAmarelo.desenhaImagem();
	carrinhoAzul.desenhaImagem();
	carrinhoPolicia.desenhaImagem();
},50);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/carros/11-objetos.html

Dilminha e carros

Movendo o mundo

Temos um problema: nem o personagem principal nem os carros estão movimentando-se. Precisamos efetuar algumas alterações no código.

No construtor Sprite, vamos adicionar uma função move, que recebe as variações das posições x e y e trata os limites:

function Sprite(caminhoDaImagem, xInicial, yInicial) {

	//restante do código...

	this.move = function(dx, dy) {
		this.x += dx;
		this.y += dy;
		//limites
		if(this.x > canvas.width) {
			this.x = -this.largura;
		} else if(this.x < -this.largura) {
			this.x = canvas.width;
		}
		if(this.y > canvas.height - this.altura + 5) {
			this.y -= dy;
		} else if(this.y <= -5) {
			this.y = canvas.height - this.altura;
		}
	}
}

No trecho de código que trata o pressionamento das setas do teclado, vamos usar o objeto do personagem principal:

document.onkeydown = function(event) {
	switch(event.which) {
	case 37: //pra esquerda
		dilminha.move(-10, 0);
		break;
	case 38: //pra cima
		dilminha.move(0, -10);
		break;
	case 39: //pra direita
		dilminha.move(10, 0);
		break;
	case 40: //pra baixo
		dilminha.move(0, 10);
		break;
	}
}

No fim da função do setInterval, vamos fazer com que os carrinhos amarelo e o da polícia movam-se para a direita e com que o azul mova-se para a esquerda (usando um valor negativo). Quanto maior o valor passado, mais rápido o carro será movido:

setInterval(function(){
	//desenha fundo e imagens...

	carrinhoAmarelo.move(7, 0);
	carrinhoAzul.move(-5, 0);
	carrinhoPolicia.move(10, 0);
},50);

Os limites horizontais são tratados na função move do Sprite, fazendo com que os carros apareçam à esquerda ao ultrapassar o limite da direita e vice-versa. O efeito da movimentação é visualizado na próxima chamada da função do setInterval.

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/carros/12-move-tudo.html

Movendo Dilminha e carros

Tem mais

Movemos nosso personagem principal a partir do teclado. E os carros são movidos automaticamente.

Mas veja no gif acima: a colisão do nosso personagem com o carro da polícia não ocasiona nada. Deveria ser um GAME OVER. Esse é o assunto da próxima semana!

Criando um jogo em Javascript [2/5] – Personagem principal

Vamos continuar a nossa versão em JavaScript do Freeway, aquele jogo do Atari da galinha atravessando a rua.

No post anterior, fizemos a rua, calçadas e faixas que ficarão como pano de fundo.

Agora colocaremos a figura do personagem principal.

Inserindo uma imagem

No Freeway criado pelo Henrique Lobo Weissmann, a galinha foi criada a partir de uma matriz de cores (ou bitmap).

Aqui, vamos tomar uma abordagem mais simples: usaremos uma imagem já pronta. Apresento-lhes a Dilminha:

Dilminha, a personagem principal

Essa imagem pode ser baixada em:
http://a-dilminha.appspot.com/dilminha.png

Nota: quem tiver os direitos autorais dessa imagem, por favor entre em contato.

Criaremos um novo Image, com a URL da imagem no atributo src:

var imagem = new Image();
imagem.src = "../../dilminha.png";

Para inserir a imagem no fundo, usaremos o contexto 2D que foi criado a partir do canvas. Passaremos a imagem para a função drawImage do contexto, além das posições iniciais (x e y) e largura e altura da imagem:

var x = 320;
var y = 400;
contexto.drawImage(imagem, x, y, imagem.width, imagem.height);

Se tentarmos executar o código acima, veremos que a imagem não foi exibida. Porque será? É que só podemos colocá-la no contexto depois dela ter sido carregada. Para isso, devemos colocar uma função na propriedade onload da imagem que será executada depois do carregamento:

imagem.onload = function(){
    contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/5-figura.html

Imagem da Dilminha sobre o fundo

Movendo o personagem

Com a imagem inserida sobre o fundo, precisamos movê-la ao pressionar as setas do teclado. Para isso, vamos passar para a propriedade onkeydown do document uma função que será chamada quando algo for digitado:

document.onkeydown = function(){
    //executado quando algo for digitado...
}

Nessa função, recebemos como parâmetro um objeto que representa o evento do pressionamento de teclas. Através da propriedade which desse evento, podemos saber qual tecla foi pressionada. A seta pra esquerda tem o código 37; pra cima, o código é 38; pra direita, 39; para baixo, 40.

Os eixos x e y são contados da esquerda pra direita e de baixo pra cima. Quanto maior o valor, mais para a direita e mais para baixo estará a imagem. Por isso, mover para a direita é aumentar o valor de x; pra esquerda é diminuir. Mover para cima, é diminuir o y; pra baixo, é aumentar.

Então, ficaríamos com o código:

document.onkeydown = function(event) {
    switch(event.which) {
        case 37: //pra esquerda
            x = x - 10;
        break;
        case 38: //pra cima
            y = y - 10;
        break;
        case 39: //pra direita
            x = x + 10;
        break;
        case 40: //pra baixo
            y = y + 10;
        break;
    }
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/6-move.html

Mas não moveu...

Por que não moveu?

Ao executarmos o código acima, vamos perceber que a imagem não foi movida. Será que esse código não funciona?

Bom… Quando o usuário pressiona as setas do teclado, o x e o y foram atualizados. O que precisamos fazer é redesenhar a imagem periodicamente, para que as novas posições sejam exibidas.

Para executar algo periodicamente no JavaScript, devemos usar a função setInterval. Precisam ser passados como parâmetros a função que será chamada e o número de milissegundos de espera entre as chamadas.

No nosso caso, a função terá o trecho de código que desenha a imagem no contexto 2D. Serão utilizados o x e o y calculados quando o usuário pressiona alguma seta do teclado. Nosso intervalo de espera será de 50 milissegundos:

setInterval(function(){
    contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
}, 50);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/7-recarrega.html

Redesenha a imagem a cada 50 milissegundos

Limpando o fundo

Perceba na imagem anterior que é deixado um rastro de todas as posições da nossa imagem. Isso acontece porque estamos redesenhando a imagem nas novas posições se limpar as imagens anteriores.

Uma maneira de fazer isso é desenhar o fundo novamente toda vez que formos atualizar a posição da imagem.

Para poder reaproveitar o código que desenha o fundo, vamos colocá-lo dentro de uma função chamada desenhaFundo. Essa função será invocada imediatamente, logo que a página for carregada, além de periodicamente, a cada 50 ms.

Vamos aproveitar e criar uma função desenhaImagem, que será responsável por desenhar a imagem no contexto 2D. Essa função também será invocada imediatamente e periodicamente.

O código reorganizado ficaria assim:

function desenhaFundo () {
    //preenche o fundo com cinza escuro
    contexto.fillStyle = "dimgray";
    contexto.fillRect(0, 0, canvas.width, canvas.height);

    //calcada superior
    contexto.fillStyle = "lightgray";
    contexto.fillRect(0, 0, canvas.width, 80);

    //calcada inferior
    contexto.fillStyle = "lightgray";
    contexto.fillRect(0, 380, canvas.width, 100);

    //faixas
    contexto.fillStyle = "white";
    for(var i = 0; i < 25; i++){
        contexto.fillRect(i*30-5, 185, 20, 4);
        contexto.fillRect(i*30-5, 280, 20, 4);
    }
}

function desenhaImagem(){
    contexto.drawImage(imagem, x, y, imagem.width, imagem.height);
};

var imagem = new Image();
imagem.src = "../../dilminha.png";
var x = 320;
var y = 400;
imagem.onload = desenhaImagem;

//código que trata teclas pressionadas omitido...

setInterval(function(){
    desenhaFundo();
    desenhaImagem();
},50);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/8-limpando.html

Movendo imagem depois de limpar fundo

Impondo limites

Do jeito que foi feito o código que trata as teclas digitadas, os valores de x e y são adicionados ou subtraídos indefinidamente. Isso faz com que não existam limites e a imagem desapareça da tela. Por exemplo:

Imagem quase sumindo do canto inferior direito da tela

Seria interessante que limitássemos os valores possíveis de x e y. Por exemplo, podemos definir um limite superior e fazer com que, quando ultrapassado, a imagem aparece na parte debaixo da tela. Poderíamos criar também limites horizontais: quando a imagem ultrapassar o limite da direita, aparecerá a esquerda e vice-versa. O limite inferior será fixo, não fazendo com que seja tomado um atalho para o topo da tela.

Uma implementação desses limites, seguindo as regras acima, seria:

document.onkeydown = function(event) {
    switch(event.which) {
        case 37: //pra esquerda
            x = x -10;
            //passado o limite da esquerda, aparecerá à direita
            if(x < -imagem.width) {
                x = canvas.width;
            }
        break;
        case 38: //pra cima
            y = y - 10;
            //passado o limite superior, aparecerá embaixo
            if(y <= -5) {
                y = canvas.height - imagem.height;
            }
        break;
        case 39: //pra direita
            x = x + 10;
            //passado o limite da direita, aparecerá à esquerda
            if(x > canvas.width) {
                x = -imagem.width;
            }
        break;
        case 40: //pra baixo
            y = y + 10;
            //se o novo y passou o limite inferior, desfaz a soma
            if(y > canvas.height - imagem.height + 5) {
                y -= 10;
            }
        break;
    }
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/movendo/9-limites.html

Limite inferior

Tem mais

Semana que vem, teremos carros que se moverão sozinhos. E iremos organizar um pouco o código. Até mais!

Criando um jogo em Javascript [1/5] – Fundo

Por que a galinha atravessou a rua?

Certa vez, durante uma de minhas aulas, um aluno disse que um dos seus objetivos era fazer um joguinho. Pergunto se poderia ajudá-lo. Claro!

Jogos são complicados de programar. Muito esforço e conhecimento são necessários.

Pensei sobre um jogo simples, pra facilitar a implementação o máximo possível. Pelas restrições da tecnologia da época, os jogos do Atari não tinha grandes complicações. Aí, lembrei de um post em que Henrique Lobo Weissmann implementava o Freeway, aquele jogo da galinha atravessando a rua do Atari, manipulando um canvas do HTML5 com Javascript.

Resolvi fazer a minha versão, com uma abordagem e um tema um pouco diferentes.

Cenário com canvas do HTML5

Para fazer o cenário do jogo, vamos utilizar a tag canvas do HTML5:

<html>
  <body>
    <canvas id="canvas" width="640" height="480"
      style="border: solid 1px black; margin: 0px auto; display: block;"></canvas>
  </body>
</html>

É importante declarar a largura (width) e altura (height) diretamente no canvas, para que os desenhos não fiquem distorcidos. Através do atributo style, adicionamos uma borda, retiramos as margens superior e inferior e fizemos o canvas ocupar a linha toda. Não é a maneira ideal de declarar CSS, mas pra gente é o suficiente…

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/1-canvas.html.

1-canvas

Colorindo o fundo com JS

Vamos colorir o canvas com um cinza escuro, para que seja o asfalto do nosso cenário.

Para isso, utilizaremos JavaScript, declarado dentro de uma tag script:

<html>
  <body>
    <canvas ...></canvas>
    <script>
      //nosso js aqui...
    </script>
  </body>
</html>

Nosso código JavaScript ficará diretamente no HTML. Não é o ideal, mas serve para o nosso caso…

Dentro da tag script, vamos obter o objeto DOM do canvas pelo id e, a partir desse, um context 2D, que podemos utilizar para desenhar pontos, retângulos, pixels e outras coisas.

var canvas = document.getElementById("canvas");
var contexto = canvas.getContext("2d");

A partir do context, vamos configurar a propriedade fillStyle com um cinza escuro e desenhar um retângulo, preenchendo todo a largura e altura do canvas, com a função fillRect.

//preenche o fundo com cinza escuro
contexto.fillStyle = "dimgray";
contexto.fillRect(0, 0, canvas.width, canvas.height);

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/2-fundo.html

2-fundo

Desenhando as calçadas

Usaremos um cinza mais claro para preencher um retângulo no topo e um no fim do canvas que serão as calçadas:

//calcada superior
contexto.fillStyle = "lightgray";
contexto.fillRect(0, 0, canvas.width, 80);

//calcada inferior
contexto.fillStyle = "lightgray";
contexto.fillRect(0, 380, canvas.width, 100);

Os parâmetros da função fillRect são:

  • o x inicial, da esquerda para a direita
  • o y inicial, do topo para baixo
  • a largura do retângulo em px
  • a altura do retângulo em px

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/3-calcadas.html

3-calcadas

Desenhando as faixas da rua

Através do fillRect, vamos desenhar pequenos retângulos brancos de 20 px de largura e de 4 px de altura. Teremos duas faixas, uma superior e uma inferior, separando a rua em três pistas.

//faixas
contexto.fillStyle = "white";
for(var i = 0; i < 25; i++){
  //faixa superior
  contexto.fillRect(i*30-5, 185, 20, 4);

  //faixa inferior
  contexto.fillRect(i*30-5, 280, 20, 4);
}

O resultado do código acima está disponível em:
http://a-dilminha.appspot.com/passo-a-passo/iniciando/4-faixas.html

4-faixas

Tem mais

Fique ligado(a), que não acabamos ainda! Esse é apenas o primeiro de uma série de posts!

Pacote java.time com JAX-RS no Wildfly 8.2

Temos a seguinte API de lista de tarefas feita com JAX-RS:

@Path("/tarefas")
public class TarefasResource {
  @Inject
  private TarefasRepository repo;

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  public Response nova(Tarefa t) throws URISyntaxException {
    repo.cria(t);
    return Response.created(new URI("/tarefas/" + t.getId()))
      .entity(t)
      .build();
  }

  @GET
  @Path("/{id}")
  @Produces(MediaType.APPLICATION_JSON)
  public Tarefa lista(@PathParam("id") Integer id) {
    return repo.busca(id);
  }
}

Uma tarefa tem as propriedades id, do tipo Integer descricao, do tipo String e data, do tipo LocalDate do pacote java.time:

public class Tarefa {
  private Integer id;
  private LocalDate data;
  private String descricao;
  //getters e setters...
}

A API de estilo REST define o recurso /tarefas. Para criar uma nova tarefa deve ser enviado um POST para a URI /tarefas com uma representação da tarefa em JSON. Para obter um JSON com a tarefa de id 1, deve ser enviado um GET para a URI /tarefas/1.

O código acima foi implantado em um Wildfly 8.2.0.Final.

Problemas ao serializar objetos do java.time de/para JSON

Mas há algo de estranho.

Ao enviarmos um GET para /tarefas/1, obtemos o seguinte JSON:

{
  "id": 1,
  "descricao": "Configurar JAX-RS",
  "data": {"year":2016,"month":"FEBRUARY","chronology":{"calendarType":"iso8601","id":"ISO"},"era":"CE","dayOfYear":56,"dayOfWeek":"THURSDAY","leapYear":true,"dayOfMonth":25,"monthValue":2}
}

A representação do LocalDate em JSON ficou gigantesca! Vários detalhes internos foram exibidos…

E se tentarmos enviar um POST para /tarefas com o JSON abaixo?

{
  "data": "2016-02-26",
  "descricao": "Configurar Wildfly"
}

O resultado será um erro 400 (Bad Request) com a seguinte mensagem:

com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDate] from String value ('2016-02-26'); no single-String constructor/factory method
 at [Source: io.undertow.servlet.spec.ServletInputStreamImpl@1c6dc29c; line: 1, column: 2] (through reference chain: br.com.alexandreaquiles.modelo.Tarefa["data"])

A mensagem de erro acima informa que o Jackson, a biblioteca de serialização JSON usada pelo Wildfly, não conseguiu transformar a String em um LocalDate.

Como fazer para ensinar o Jackson a trabalhar com um objeto do tipo LocalDate ou de outras classes do pacote java.time como se fossem Strings?

Melhorando a (de)serialização

O Wildfly 8.2.0.Final usa a versão 2.4.1 do Jackson, que tem a extensão jackson-datatype-jsr310, responsável por serializações mais interessantes de/para classes do pacote java.time.

Se estivermos usando o Maven, basta adicionar mais uma dependência:

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.4.1</version>
</dependency>

Outras ferramentas de gerenciamento de dependência teriam configurações análogas. Caso não esteja usando nenhuma ferramenta do tipo, baixe o jar.

Ao testarmos novamente, os mesmos erros acontecem… Que chato!

É que precisamos configurar um ContextResolver, anotando-o com @Provider e registrando o módulo JSR310Module do Jackson:

@Provider
public class JacksonJavaTimeConfiguration implements ContextResolver<ObjectMapper> {
  private final ObjectMapper mapper;

  public JacksonJavaTimeConfiguration() {
    mapper = new ObjectMapper();
    mapper.registerModule(new JSR310Module());
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
  }

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return mapper;
  }
}

Note a configuração WRITE_DATES_AS_TIMESTAMPS setada para false. Se não fizermos isso, as datas são representadas como um array no JSON, ao invés de um texto.

Agora, se enviarmos um GET para /tarefas/1, obtemos:

{
  "id": 1,
  "descricao": "Configurar JAX-RS",
  "data": "2016-02-25";
}

O LocalDate passa a ser representado no JSON como uma String no formato ISO-8601.

Ao enviarmos o POST para /tarefas novamente, tudo dá certo! Recebemos um status 201 (Created).

Funciona em outro servidor de aplicação?

Infelizmente, não!

A grande questão é que nossa configuração foi feita para a biblioteca usada pelo Wildfly, o Jackson, inclusive em uma versão específica.

O Glassfish, por exemplo, usa a biblioteca MOXy para serialização de/para JSON. As configurações seriam diferentes…

Dependências do Java EE 7 no Maven

Um projeto que usa o Maven ganha um monte de facilidades, da compilação a geração dos entregáveis em apenas um comando.

Vamos supor que temos o seguinte pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>br.com.alexandreaquiles</groupId>
  <artifactId>exemplo</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </properties>

  <dependencies>
    <!-- aqui ficam as dependências -->
  </dependencies>
</project>

Considere que nosso projeto tem a seguinte classe:

@Stateless
@Path("/produtos")
public class ProdutosResource {
  @Inject
  private EntityManager em;
  @GET
  public List<Produto> lista(){
    return em.createQuery("select p from Produto p", Produto.class).getResultList();
  }
}

Usamos algumas funcionalidades do Java EE 7 como EJBs (@Stateless), JPA (EntityManager) e JAX-RS (@Path e @GET).

A questão é: como devemos declarar as dependências dessas API do Java EE 7? Será que precisamos declarar cada API utilizada, uma a uma?

Dependência do Java EE 7

Não! Há uma dependência publicada no repositório central do Maven que disponibiliza as APIs do Java EE 7:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-api</artifactId>
  <version>7.0</version>
  <scope>provided</scope>
</dependency>

Perceba o escopo provided acima. Estamos indicando que essas APIs devem ser utilizadas apenas para compilação e não precisam ser incluídas no entregável (WAR, no nosso caso).

É importante notar que passamos a depender apenas das APIs e não de nenhuma implementação. Isso é muito interessante se desejarmos mudar de servidor de aplicação. Podemos gerar o WAR e implantá-lo no Wildfly, no Glassfish e em qualquer servidor que implemente o Java EE 7.

Dependência do Java EE 7 Web Profile

Na verdade, a dependência acima é da versão Full do Java EE, fazendo com que possamos usar JMS, JCA, entre outros. No nosso caso, é suficiente depender do Web Profile, que possui menos funcionalidades mas tem o que precisamos. Então, poderíamos usar:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-web-api</artifactId>
  <version>7.0</version>
  <scope>provided</scope>
</dependency>

Dependências mantidas pela JBoss

A empresa JBoss, que mantém os servidores de aplicação Wildfly e JBoss EAP e vários outros projetos, disponibiliza dependências alternativas para as APIs do Java EE 7.

Para ter como dependência as APIs do Java EE 7 Full:

<dependency>
  <groupId>org.jboss.spec</groupId>
  <artifactId>jboss-javaee-7.0</artifactId>
  <version>1.0.0.Final</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>

Já para as do Java EE 7 Web Profile:

<dependency>
  <groupId>org.jboss.spec</groupId>
  <artifactId>jboss-javaee-web-7.0</artifactId>
  <version>1.0.0.Final</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>

As dependências acima não são para a implementação do Wildfly ou do JBoss EAP. São dependências para as APIs do Java EE alternativas às vistas anteriormente.

Métodos default, colisão de interfaces e precedência de superclasses no Java 8

Métodos públicos e abstratos em Interfaces

Imagine que temos a interface Aquatico, que define um método move:

interface Aquatico {
	public abstract void move();
}

Se tivermos uma classe Navio que implementa essa interface mas não fornece uma implementação para o método move, teremos um erro de compilação:

class Navio implements Aquatico {
^
error: Navio is not abstract and does not override abstract method move() in Aquatico
}

Uma interface nos obriga a respeitar um contrato, ou seja, a assinatura (nome, parâmetros e retorno) de um ou mais métodos.

No Java, desde os primórdios, os métodos das interfaces são implicitamente public e abstract. Essa última palavra-chave indica que são métodos abstratos, ou seja, sem corpo definido e sem código, meros moldes a serem preenchidos.

Para o código da classe concreta Navio ser compilado com sucesso, temos que fornecer uma implementação para o método move. Por exemplo:

class Navio implements Aquatico {
	public void move() {
		System.out.println("Navega na água!");
	}
}

O código acima compila normalmente e podemos invocá-lo de maneira polimórfica, como a seguir:

public class Programa {
	public static void main(String[] args){
		Aquatico titanic = new Navio();
		titanic.move(); //imprime "Navega na água!"
	}
}

Métodos com mesma assinatura em Interfaces diferentes

Considere que temos uma interface Terrestre, conforme abaixo:

interface Terrestre {
	public abstract void move();
}

Poderíamos criar um classe Caminhao que implementa a interface Terrestre, de maneira semelhante ao que fizemos acima:

class Caminhao implements Terrestre {
	public void move() {
		System.out.println("Move na longa estrada da vida!");
	}
}

Para utilizar a classe Caminhao, faríamos de maneira parecida com a anterior:

public class Programa {
	public static void main(String[] args){
		Caminhao scania = new Caminhao();
		scania.move(); //imprime "Move na longa estrada da vida!"
	}
}

Mas o que acontece se tivermos uma classe que representa um Hovercraft, um veículo anfíbio, que pode se deslocar tanto na água como na terra?

Será que podemos implementar tanto a interface Aquatico como a interface Terrestre simultaneamente?

Claro que sim, já que a interface define apenas a assinatura (ou seja, nome, parâmetros e retorno) do método move e não uma implementação.

class Hovercraft implements Aquatico, Terrestre {
	public void move(){
		System.out.println("Move na água e na terra!");
	}
}

Não há problema algum, porque a implementação do método move na classe Hovercraft é a mesma tanto para Aquatico como para Terrestre.

Podemos usar a classe Hovercraft conforme a seguir:

public class Programa {
	public static void main(String[] args){
		Hovercraft srn4 = new Hovercraft();
		srn4.move(); //imprime "Move na terra e na água!"
	}
}

Métodos default em Interfaces no Java 8

Conforme vimos em um post anterior, no Java 8 agora é possível definir, além de contratos, implementações de métodos em interfaces.

Esse novo mecanismo é chamado de métodos default e, com ele, é possível evoluir interfaces antigas como a Iterable, que agora possui um método forEach, por exemplo.

No exemplo acima, poderíamos definir o comportamento de navegar na água diretamente na interface Aquatico, utilizando a palavra reservada default:

interface Aquatico {
	public default void move() {
		System.out.println("Navega na água!");
	}
}

Métodos default só podem ser definidos em interfaces e são implicitamente públicos e, é claro, não-abstratos porque possuem um corpo definido, com código.

A classe Navio já não precisará fornercer uma implementação para o método move:

class Navio implements Aquatico {
}	

A interface Terrestre também pode ter uma implementação do método move com um método default:

interface Terrestre {
	public default void move() {
		System.out.println("Move na longa estrada da vida!");
	}
}

A classe Caminhao também seria simplificada:

class Caminhao implements Terrestre {
}

O programa que utiliza as classes Navio e Caminhao, bem como suas respectivas interfaces, não precisa ser alterado:

public class Programa {
	public static void main(String[] args){
		Aquatico titanic = new Navio();
		titanic.move(); //imprime "Navega na água!"

		Caminhao scania = new Caminhao();
		scania.move(); //imprime "Move na longa estrada da vida!"
	}
}

Colisão de métodos default

Mas o que será que acontece com a nossa classe Hovercraft, que implementa tanto Aquatico como Terrestre?

class Hovercraft implements Aquatico, Terrestre {
^
error: class Hovercraft inherits unrelated defaults for move() from types Aquatico and Terrestre

Observe que, para a classe Hovercraft, o código não pode ser compilado. Como com um método default não apenas define uma assinatura mas também uma implementação, há ambiguidade na definição do método move. O compilador não pode definir qual das duas implementações deve ser escolhida e, por isso, nos avisa dessa colisão com um erro de compilação.

Para fazermos a classe Hovercraft compilar com sucesso, temos que definir uma implementação para o método move, evitando qualquer ambiguidade:

class Hovercraft implements Aquatico, Terrestre {
	public void move() {
		System.out.println("Move na terra e na água!");
	}
}

Uma alternativa é invocar um método default de uma interface a partir de uma classe que a implementa, utilizando o nome da interface seguido de super e do nome do método default. Por exemplo:

class Hovercraft implements Aquatico, Terrestre {
	public void move() {
		Aquatico.super.move();
		Terrestre.super.move();
		System.out.println("Move na terra e na água!");
	}
}

Se utilizarmos a classe Hovercraft acima em um programa, teremos algo como:

public class Programa {
	public static void main(String[] args){
		Hovercraft srn4 = new Hovercraft();
		srn4.move(); 
		// imprime 
		//	"Navega na água!"
		//	"Move na longa estrada da vida!"
		//	"Move na terra e na água!"
	}
}

Precedência de métodos da superclasse

Suponha que tenhamos uma interface Aereo, com um método default chamado move:

interface Aereo {
	public default void move() {
		System.out.println("Move no ar!");
	}
}

Também temos uma classe Aviao que implementa a interface Aereo, porém sobreescrevendo o método move:

class Aviao implements Aereo {
	public void move(){
		System.out.println("Voa...");
	}
}

Se criarmos um objeto da classe Aviao, qual método será chamado: o método sobreescrito na classe ou o método default da interface?

Será escolhido o método sobreescrito na classe Aviao, funcionando da mesma maneira que sobreescrita de métodos para classes:

public class Programa {
	public static void main(String[] args){
		Aviao boeing747 = new Aviao();
		boeing747.move(); //imprime "Voa..."
	}
}

Agora, se quisermos representar um Hidroavião, um tipo de avião que pode decolar e pousar na água, teremos uma classe semelhante a seguinte:

class HidroAviao extends Aviao implements Aquatico {
}

A classe HidroAviao é um Aviao que implementa a interface Aquatico.

Mas o que será que acontece ao invocarmos o método move de uma instância de HidroAviao? Será que é invocado a implementação da superclasse Aviao ou da interface Aquatico?

A implementação escolhida será a da superclasse:

public class Programa {
	public static void main(String[] args){
		HidroAviao curtissNC = new HidroAviao();
		curtissNC.move(); //imprime "Voa..."
	}
}

Uma implementação definida em uma superclasse sempre tem precedência sobre uma implementação definida em um método default de uma interface.

É importante lembrar que, em Java, uma classe só pode ter uma superclasse.

Resumindo

As regras do uso de métodos default são bem claras:

  1. Colisão de interfaces – se uma classe implementa duas interfaces que possuem métodos default com a mesma assinatura, o método deve ser sobreescrito
  2. Precedência de superclasses – se uma superclasse provê uma implementação de um método que tem a mesma assinatura de um método default de uma interface, a implementação da superclasse tem precedência