quinta-feira, 9 de outubro de 2008

Imagem de fundo no captcha gerado pelo simple_captcha

O simple_captcha, plugin de geração de captcha para rails, não possui opção para colocar um imagem de fundo na imagem gerada. Mas fazer isso não é nenhum bicho de sete cabeças, basta alterar algumas partes chaves do plugin utilizando um pouco de rmagick. Vou mostrar aqui mais ou menos como fiz isso em um recente projeto.

No arquivo simple_captcha_image.rb basta alterar o método generate_simple_captcha_image para que na criação da imagem seja colocado o fundo desejado. Veja o exemplo de código abaixo:

def generate_simple_captcha_image(options={})  #:nodoc

fundo = Magick::Image.read(url_da_imagem_de_fundo).first
preenchimento = Magick::TextureFill.new(fundo)

@image = Magick::Image.new(197, 45, preenchimento) do
self.format = 'JPG'
end

# ...

Repare que existe ali uma chamada ao método url_da_imagem_de_fundo que no caso é um método que pega o path de uma imagem aleatória. Dessa forma o captcha poderá ter vários fundos diferentes. Veja um exemplo de como pode ser a implementação desse método:

  def url_da_imagem_de_fundo
"#{RAILS_ROOT}/vendor/plugins/simple_captcha/assets/imgs/picture_" + (rand(9)+1).to_s + ".jpg"
end

Foi preciso também comentar a chamada ao método que aplica estilo e distorção à imagem, mas não sei bem se isso é necessário. Como fiz isso, precisei alterar o método append_simple_captcha_code que escreve o texto na imagem, de forma a utilizar diversos tamanhos, posições e fontes diferentes.

quarta-feira, 8 de outubro de 2008

Validação no lado cliente com form_remote_tag

Para fazer validações de formulário no lado cliente basta informar ao método form_tag ou ao form_for, utilizando o parâmetro :onsubmit, o código javascript que se deseja executar para validá-lo. Veja no exemplo:

  <% form_tag "/entrar", :onsubmit => 'return valida(this)' do %>
<h1>ok</h1>
<% end %>

Contudo para a tag form_remote_tag e form_remote_for não há a possibilidade de utilizar o parâmetro :onsubmit. Isso porque o formulário gerado pelo rails já possui a ação onsubmit definida com o código que fará o acesso remoto. Para colocar então sua validação antes deste código deve-se usar o parâmetro :before como é mostrado no exemplo:

  <% form_remote_tag :url => "/entrar", 
:update => 'meu_div',
:before => 'if( !valida(this) ) return false' do %>
<h1>ok</h1>
<% end %>

Repare que é importante que haja o "return false" no caso de não ter sido validado, caso contrário ele executará o restante do código colocado pelo rails no onsubmit.

quinta-feira, 2 de outubro de 2008

Impedindo redeclaração de objetos javascript

Ao importar duas vezes um mesmo arquivo .js em uma página, as funções, protótipos e objetos são redeclarados. Se for necessário que um determinado objeto mantenha seu estado durante as iterações assíncronas dessa página, perde-se este estado na segunta importação do arquivo .js.

Veja por exemplo o objeto abaixo, que nada mais é que um repositório de outros objetos. Tá ta ta, não é um exemplo que tenha tanta utilidade, mas vá lá, é só pra explicar o mecanismo.
var Repositorio = {
sequencia:0,
lista:[],
registrar:function(obj) {
var id = ++this.sequencia;
this.lista[ id ] = obj;
return id;
},
recuperar:function(id) {
return this.lista[ id ];
}
}
Agora estando esse código num arquivo por exemplo repositorio.js teríamos o problema relatado com o código a seguir:
<script src="repositorio.js"></script>
<script>
Repositorio.registrar( new Object() );
Repositorio.registrar( new Object() );
</script>
<script src="repositorio.js"></script>
<script>
alert( Repositorio.recuperar( 1 ) );
</script>
Para impedir essa redeclaração do objeto, e os possíveis erros que ela pode causar, basta tentar utilizá-lo dentro de um bloco try e fazer sua declaração dentro do bloco catch. Ou seja, ele só será declarado caso haja um erro na tentativa de usá-lo. Veja:
try{ Repositorio.existo(); }
catch(e) {
Repositorio = {
sequencia:0,
lista:[],
existo:function(){},
registrar:function(obj) {
var id = ++this.sequencia;
this.lista[ id ] = obj;
return id;
},
recuperar:function(id) {
return this.lista[ id ];
}
}
}
Mas aí você poderia me dizer: Pô pra que tudo isso, basta não importar duas vezes o javascript. Acontece que se você está criando uma biblioteca para ser distribuída para diversos sites e blogs, você não deve subestimar a "criatividade" dos utilizadores dela. Então, o melhor é se precaver.

quinta-feira, 18 de setembro de 2008

Cópia exclusiva de coleção

Por serem de díficil identificação, problemas de concorrência acabam indo pra produção sem sequer serem notado em ambiente de testes e desenvolvimento. Erros acabam ocorrendo de maneira intermitente, causando dores de cabeça e acessos de insanidade nos desenvolvedores. Martin Fowler em seu livro Patterns of Enterprise Application Architecture identifica algumas soluções para evitar problemas de concorrência relativos a integridade de dados. Contudo há um outro problema que não está catalogado neste livro: A concorrência de informações na memória.

Ao guardar objetos em uma coleção que é compartilhada por outras threads, é necessário tomar providências para que não sejam lançadas exceções inesperadas. A medida básica é garantir que os pontos de acesso às coleções compartilhadas devem obter exclusividade sobre seu uso com a diretiva synchronized. Veja abaixo um exemplo de classe que torna o uso de uma lista à prova de erros de concorrência:

public class ListaSegura {
private List lista = new ArrayList();
public void adiciona(Object objeto) {
synchronized (lista) {
lista.add(objeto);
}
}
public void remove(Object objeto) {
synchronized (lista) {
lista.remove(objeto);
}
}
public void listar() {
synchronized (lista) {
for (Object o : lista) {
System.out.println(o);
}
}
}
}

É uma solução simples e que resolve em parte o problema. Contudo na maioria das vezes não podemos obter a exclusividade para iterar sobre uma coleção. No caso mostrado acima isso é possível pois estamos apenas imprimindo o objeto. Mas existem alguns casos em que obter essa exclusividade para a iteração nos traria alguns problemas. Esses casos estão descritos abaixo:

1- Alteração da coleção: No caso de você iterar pela coleção para remover ou adicionar algum objeto a ela. A exclusão ou adição ocorreria no meio da iteração e com isso seria lançada uma exceção de concorrência.Um exemplo simples disso são classes que gerenciam cache e precisam periodicamente remover objetos expirados.

2- Iteração prolongada: Quando a coleção possui objetos demais ou o processo executado durante a iteração é lento, tornando o tempo de exclusividade total muito longo. Isso faria com que o restante do sistema que precisasse utilizar essa coleção ficasse muito tempo aguardando pela exclusividade terminar. Um exemplo comum é a execução de métodos que acessem banco de dados dentro da iteração de um objeto exclusivo.

Para resolver esses dois casos identifiquei o padrão de desenvolvimento Cópia Exclusiva de Coleção. A idéia é obter a exclusividade da lista somente para fazer uma cópia dos itens em outra coleção e então poder iterar sobre esta cópia sem muitas preocupações. Abaixo mostro um exemplo de uma classe Armario que possui muitas Coisas. Se alguma coisa for lixo, ela deve ser removida na execução do método removeLixo.

public class Armario {
private List coisas = new LinkedList();
public void removeLixo() {
List copia = lista();
for (Coisa coisa : copia) {
if (coisa.isLixo())
remove(coisa);
}
}
public List lista() {
List copia = null;
synchronized (coisas) {
copia = new ArrayList(coisas.size());
copia.addAll(coisas);
}
return copia;
}
public void adiciona(Coisa coisa) {
synchronized (coisas) {
coisas.add(coisa);
}
}
public void remove(Coisa coisa) {
synchronized (coisas) {
coisas.remove(coisa);
}
}
}

Com isso o tempo de exclusividade fica restrito ao tempo da cópia para a outra coleção, que ocorre no método lista. Dessa forma temos uma folga no tempo de iteração total e a possibilidade de rearranjar a coleção, adicionando ou removendo itens a ela. É importante ressaltar que o melhor jeito de evitar o calafrio de receber um java.util.ConcurrentModificationException é evitar a utilização de objetos compartilhados entre threads. Quando isso não é possível, o jeito é utilizar um padrão como Cópia Exclusiva de Coleção.

terça-feira, 16 de setembro de 2008

On-demand javascript com PHP

A técnica de script on-demand serve para compartilhar dados entre domínios. A idéia dela é driblar as restrições da classe XMLHttpRequest, que só permite requisições no mesmo domínio, e a restrição do iframe que no máximo permite comunicação entre sub-domínios de um mesmo domínio.

Basicamente consiste em adicionar um script à página quando uma informação é necessária. Esse script proverá a informação necessária chamando uma função de callback ao solicitador. Uma explicação mais detalhada sobre a técnica pode ser encontrada em On-Demand_Javascript - AjaxPatterns.

Com PHP fica ainda mais fácil desenvolver o script provedor de dados. Digamos por exemplo que você queira obter a lista de livros de uma biblioteca, seu javascript ficaria assim:
var Livros = {
listar:function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = 'listaDeLivros.php?callback=Livros.onload_listar';
document.getElementsByTagName('head')[0].appendChild(s);
}
onload_listar:function(html) {
document.getElementsByTagName('lista').innerHTML = html;
}
}

A função listar insere um javascript à página informando a ele a função javascript de callback. No PHP informaremos o html gerado chamando a função enviada no parâmetro callback. Mais ou menos como mostrado abaixo:
<?php echo $_REQUEST['callback']; ?>("<h1>Lista de livros</h1>");

Mas é claro que normalmente o html gerado não será de apenas uma linha. Além disso ele poderá ter diversos dados com aspas e outros caracteres que poderiam causar erros de javascript. Ficar se preocupando com esse tipo de coisa na diagramação do html é trabalhoso e passível de muitos erros.

Para evitar isso podemos utilizar a função ob_start, que faz com que tudo o que for escrito no output seja guardado em buffer e ao final remetido a uma função de callback do PHP. Nesta função então pode-se tratar o html e enfim escrever no real output para o cliente. Veja como ficaria:
<?php 
function captura_buffer($html) {
$html = addslashes($html);
$html = str_replace("\n", '\n', $html);
$html = str_replace("\r", '\r', $html);
return $_REQUEST['callback'] . '("' . $html . '");';
}
ob_start('captura_buffer');

$livros = funcao_ficticia_para_obter_livros();

foreach($livros as $livro) {
?>

<p>
<?php echo $livro->id ?> -
<b><?php echo $livro->titulo ?></b> -
<?php echo $livro->autor ?>
</p>

<?php } ?>

quarta-feira, 3 de setembro de 2008

KeyDown e KeyUp para applets java

Um grande problema da interface KeyListener do Java é não possuir uma maneira de saber se uma ou mais teclas estão pressionadas ao mesmo tempo, e quando elas realmente são soltas. Quando você mantém uma tecla pressionada os eventos keyPressed e keyReleased são ativados consecutivamente, e se você pressiona e segura outra tecla, o keyReleased da tecla anterior para de ser notificado, seguido por uma notificação consecutiva do keyPressed e keyReleased da nova tecla. Isso é facilmente demonstrado no applet abaixo:
public class ComProblema extends Applet implements KeyListener {
    @Override
    public void init() {
        addKeyListener(this);
    }
    public void keyPressed(KeyEvent e) {
        System.out.println("Pressionou: " + e.getKeyCode());
    }
    public void keyReleased(KeyEvent e) {
        System.out.println("Soltou: " + e.getKeyCode());
    }
    public void keyTyped(KeyEvent e) {}
}
Ao executarmos esse applet clicando e segurando a tecla "a" por alguns segundos, depois apertar e segurar a "w" por outros tantos e ao final soltar as duas, teremos o seguinte resultado:
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Soltou: 65
Pressionou: 65
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 87
Pressionou: 87
Soltou: 65
Soltou: 87
Encontrei em diversos fóruns pessoas com o mesmo problema e nenhuma solução. Então, para resolvê-lo implementei uma espécie de KeyAdapter para facilitar a notificação de quando uma tecla é apertada e quando ela é solta. O uso de exemplo dela ficaria como o mostrado abaixo:
public class SemProblema extends Applet {
    @Override
    public void init() {
        super.init();
        addKeyListener(new CumulativeKeyAdapterImpl());
    }
}
class CumulativeKeyAdapterImpl extends AcumuladorKeyAdapter {
    @Override
    public void keyDown(KeyEvent e) {
        System.out.println("Down: " + e.getKeyCode());
    }
    @Override
    public void keyUp(KeyEvent e) {
        System.out.println("Up: " + e.getKeyCode());
    }
}
O resultado fazendo a mesma combinação de teclas apertadas no teste anterior neste outro applet, seria como o mostrado a seguir:
Down: 65
Down: 87
Up: 65
Up: 87
Você também pode ver outros exemplos de applets utilizando o AcumuladorKeyAdapter aqui. Para utilizar esse recurso basta baixar os fontes e binários da timotta-api e colocar o timotta-api-applet.jar no atributo archive da sua tag applet, como demonstrado no exemplo abaixo:
<applet code="br.com.timotta.applet.MostraTeclas" 
    archive="timotta-api-applet-exemplos.jar,timotta-api-applet.jar"
    width=256 height=256 >
</applet>

segunda-feira, 18 de agosto de 2008

Passagem de array como parâmetro em subrotinas de Perl

Passar um array como parâmetro em uma subrotina de Perl pode ser muito complicado sem saber todos os conceitos da linguagem. Veja abaixo um exemplo de uma função recebendo arrays de cinco maneiras diferentes:
sub faz_nada_util
{
print "(" . $_[0] . "," . $_[1] . ")";
}

my @array_1 = (1,2);
my @array_2 = [3,4];

faz_nada_util @array_1;
faz_nada_util \@array_1;
faz_nada_util @array_2;
faz_nada_util (5,6);
faz_nada_util [7,8];

A saída será:
(1,2)
(ARRAY(0x81536b4),)
(ARRAY(0x815360c),)
(5,6)
(ARRAY(0x8176f5c),)

O importante no exemplo é verificar que quando criamos um array utilizando colchetes [] estamos retornando na verdade não o array, mas a referência a este array. Dessa forma o primeiro item de @_ seria a referência ao array. Isso é mostrado na terceira e na última linha do resultado.

Quando criamos com o uso de parênteses () temos na váriavel o array em si. Então quando passamos esta lista à subrotina, os itens são copiados um a um no array @_. Isso é demonstrado na primeira e na quarta linha do resultado. Usando a contra-barra \ indicamos no segundo print que queremos enviar a referência do array criado com parênteses.

Mas e se você fizer uma função que receba como parâmetro um texto e um array como parâmetros? Você pode fazer da seguinte maneira:
sub continua_fazendo_nada_util
{
my $nome = shift;
print $nome . ": \n";
foreach $nome ( @_ ) {
print "- " . $nome . "\n";
}
}
continua_fazendo_nada_util("tiago", (1,2,3,4));

É como se a função recebesse como parâmetro um único grande array. Esse tipo de solução funciona bem quando o último parâmetro é o array, pois podemos dar shift dos parâmetros iniciais e iterar com os restantes. Num caso por exemplo em que a função deva receber dois arrays, podemos fazer a passagem de parâmetro pela referência.
sub nada_pra_fazer_aqui
{
my @array_1 = @{$_[0]};
my @array_2 = @{$_[1]};

print "array1: \n";
foreach $a (@array_1) {
print "- $a\n";
}
print "array2: \n";
foreach $a (@array_2) {
print "- $a\n";
}

}
nada_pra_fazer_aqui [1,2,3], [4,5,6];

quinta-feira, 14 de agosto de 2008

Web Democracia: críticas e sugestões

Utilizem o espaço dos comentários para postar suas críticas e sugestões, caso precisem entrar em contato comigo, deixem email ou facebook, responderei e tomarei o cuidado para não publicar o seu contato.

terça-feira, 12 de agosto de 2008

Installed, but unpackaged files found em um RPM

Você pegou um projeto antigo, adicionou um diretório e um arquivo e alterou a sessão %install de seu SPEC para instalar esse arquivo, mas ao rodar o rpmbuild o seguinte erro é obtido:

Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/tmp/MeuPrograma-1.0.7-tiago
error: Installed (but unpackaged) file(s) found:
/usr/local/MeuPrograma/etc/configuracao.properties

No caso o diretório novo é etc e o arquivo novo é configuracao.properties. A resolução desse problema é simples, basta adicionar o novo diretório à sessão %files, para que o rpmbuild o empacote, da seguinte forma:

%files
# ... outros arquivos aqui
/usr/local/MeuPrograma/etc

terça-feira, 17 de junho de 2008

Erro esotérico ao inserir clobs e blobs no oracle

A mensagem de erro do Oracle "table or view does not exist" é simples e direta. O problema acontece quando você está fazendo uma série de inserções em uma tabela, utilizando a mesma String de SQL para todas, e lá pela sexta inserção a mensagem de erro aparece:
java.sql.SQLException: ORA-00942: table or view does not exist

Até que você descubra que pode haver algum problema com o sexto registro da tabela, você perde um bom tempo tentando encontrar algum problema de lógica ou alguma desatenção sua. Até mesmo bufa de raiva achando que é algum bug do Oracle. E de certa forma é.

No meu caso descobri que o erro acontecia quando inseria textos grandes demais em uma coluna clob. Encontrei então um documento da oracle explicando como gravar clobs no oracle utilizando JDBC. O macete é configurar o JDBC com o parâmetro SetBigStringTryClob, assim:
Properties propriedades = new Properties();
propriedades.put("user", "meuUsuario" );
propriedades.put("password", "pensaQueVouTeContar?");
propriedades.put("SetBigStringTryClob", "true");
return DriverManager.getConnection("jdbc:oracle:thin:@meuserver.com:filmes", propriedades);

Fazendo isso o estranho erro parou de acontecer. De certa forma acredito que este é um bug do Oracle, pois no mínimo a mensagem de erro deveria ser algo informando que o tamanho do valor excede o permitido na coluna. Ou até informando que não foi possivel inserir devido a erros no preenchimento da coluna tal.

Contudo, essa não foi a unica vez que este erro aconteceu comigo. Ao inserir dados em um campo blob o maldito "table or view does not exist" deu as caras novamente. No caso eu errei o tipo de dado utilizando setString. Mas dessa vez eu já estava calejado, então bastou verificar que para inserir blobs bastaria fazer algo como o mostrado abaixo:
byte[] arr = new byte[] {0,1};
ByteArrayInputStream b = new ByteArrayInputStream(arr);
statement.setBinaryStream(i, b, b.available() );

Mas que fique bem claro que o fato de eu estar inserindo clobs e blobs no banco não quer dizer que eu ache isso uma boa prática e que eu recomende. Ao contrário, acredito que seja melhor na maioria das situações que esses dados fiquem no sistema de arquivos.

sábado, 7 de junho de 2008

Cálculo da variância com Ruby

No blog Kodumaro foi publicado uma comparação de cálculos estatísticos entre linguagens imperativas, como C, e linguagens funcionais, no caso o Lisp. Os cálculos utilizados como exemplificação foram os de variância da população e variância da amostra. Como estou estudando ruby, me empolguei e resolvi fazer a versão desses cálculos nessa linguagem.

Veja como o código ficou:
class Array
def acumula()
acc = 0
each { |a| acc += yield(a) }
acc
end
def media()
total = acumula { |a| a }
total / length
end
def numerador_de_variancia()
m = media
acumula { |a| (a - media)**2 }
end
def variancia_populacional()
numerador_de_variancia / length
end
def variancia_da_amostra()
numerador_de_variancia / (length-1)
end
end
O que fiz foi criar novos métodos na classe Array para fazer todos os cálculos necessários. O interessante é que não encontrei na documentação do Ruby nenhum método que iterasse sobre um array e retornasse um acumulo dos valores calculados por um bloco. Para suprir isso criei então o método acumula.

Apesar do código ter ficado um pouco mais verboso que o código em lisp exibido no Kodumaro, o uso ficou mais simples. Como são novos métodos, para obter a variância da amostra em um array basta por exemplo fazer [2,4,6].variancia_da_amostra. Veja abaixo um exemplo de uso desses métodos:
array = [2,4,6]
puts "Media: #{array.media}"
puts "Numerador de variancia: #{array.numerador_de_variancia}"
puts "Variancia populacional: #{array.variancia_populacional}"
puts "Variancia da amostra: #{array.variancia_da_amostra}"
Mas é importante ressaltar o perigo dessa alteração de classe. Veja que não faço nenhum tratamento sobre o tipo de dado do array. No caso, estou assumindo que todo array que vá utilizar esses métodos tenham sempre todos os indices preenchidos por numeros.

EDS: Excell Driven Scrum

Para quem não sabe, EDS é o Excell Driven Scrum. Ou vocês achavam que siglas com a palavra mágica Driven serviam somente para Development? Estavam enganados. O EDS ocorre quando o P.O. elabora previamente um planejamento, sprint a sprint, do que deve ser feito pelo time para um longo período de tempo. Este planejamento é então gravado em um arquivo excell read-only.

Pelo pouco que entendo de Scrum, esse padrão é uma maneira muito equivocada de se pôr em prática um ambiente de desenvolvimento ágil. Isso porque a definição dos sprints deveria ser feita com a particiação do time. E mesmo se esse tal planejamento anual fosse feito com o time, ainda sim eu consideraria errado, pois é impossivel prever com muita antecedência fatores ambientais que fatalmente mudam as necessidades e problemas que precisam ser resolvidos.

Além disso, o fato de termos escopos definidos para cada sprint, faz o sprint planning 1 ficar engessado. O time perde a liberdade de sugerir histórias prioritárias que garantam a qualidade do sistema. Isso acaba causando um desgaste durante a reunião, o time não entendendo como o P.O. pode ignorar os possiveis problemas da aplicação e os riscos que eles podem causar, e o P.O. sem entender porque o time não aceita seguir o planejamento anual.

Acontece que muitas vezes o uso deste padrão não é culpa do P.O., é uma parte da cultura anterior à adoção do Scrum que ainda não foi possivel mudar. Contudo, não se pode fechar os olhos para este problema. Para acabar com o EDS, é preciso que o P.O. brigue corajosamente contra ele. O time também precisa bater o pé o máximo possivel para mudar esta cultura. E a briga não é apenas para mudar a cabeça de quem está acima hierarquicamente, é também para mudar suas próprias mentes.

Portanto, apesar de ter pouca experiência com scrum, acredito que utilizar o padrão EDS não é uma boa alternativa de implantação dele. Pela minha vivência de menos de um ano e a participação em apenas um curso, me parece que um dos principais benefícios que o Scrum traz é a liberdade que o time tem para identificar problemas e resolve-los para garantir a qualidade do produto. Com o EDS isso não é possivel.

sexta-feira, 6 de junho de 2008

Como obter a URL do SWF

Essa aqui é básica, mas eu procurei bastante no Google e não encontrei. Acabei só achando no help do próprio flash. Caso você precise obter no seu aplicativo flash qual é o caminho completo do seu SWF, basta utilizar a propriedade loaderURL do objeto loaderInfo contido no seu MovieClip. Veja abaixo:

package {
public class Main extends MovieClip {
function Main() {
trace( "URL do SWF: " + this.loaderInfo.loaderURL );
}
}
}

quinta-feira, 15 de maio de 2008

FullScreen do flash não funciona em players abaixo de 9.0.28, e agora?

Desde a versão 9.0.28 do flash é possível definir seu swf para rodar em tela cheia de maneira bem simples. Também é possivel definir um listener para ser notificado quando o modo de exibição do flash é alterado de normal para fullscreen e vice-versa. Contudo, se você colocar essas funcionalidades, seu swf não rodará em versões anteriores a 9.028.

O jeito então é alterar seu ActionScript para só setar a o fullscreen quando a versão for compatível. Veja abaixo como isso pode ser feito:
import flash.display.StageDisplayState;

public class Main extends MovieClip {

//...

function suportaTelaCheia():Boolean {
//Verifica versao >= que 9.0.28
}
function onBotaoTelaCheiaClique(event:MouseEvent):void {
if( suportaTelaCheia() ) {
stage.displayState = StageDisplayState.FULL_SCREEN;
}
}
function onBotaSaiTelaCheiaClique(event:MouseEvent):void {
if( suportaTelaCheia() ) {
stage.displayState = StageDisplayState.NORMAL;
}
}

//...
}
Apesar da classe flash.display.StageDisplayState não existir no player com versão menor que 9.0.28, a importação dela não trará erro, pois o flash só executa a importação de uma classe quando faz uso dela. Portanto a seleção da versão feita com o método suportaTelaCheia impede seu uso e consequente importação.

O problema maior é quando precisamos definir um listener para sabermos quando o swf entrou em modo fullscreen e quando saiu. Normalmente isso é necessário quando precisamos redimensionar, ou adicionar elementos ao stage. No caso o código, sem o tratamento de versão, ou seja, o que daria erro nas versões de flash anteriores a 9.0.28 ficaria assim:
import flash.events.FullScreenEvent;

public class Main extends MovieClip {

//...

private function onStage(event:Event):void {
stage.addEventListener( FullScreenEvent.FULL_SCREEN, redrawFullScreen );
}

//...

function redrawFullScreen(event:FullScreenEvent):void {
if( event.fullScreen ) {
//Posicionar elementos para tela cheia
} else {
//Posicionar elementos para tamanho normal
}
}

//....

}
O grande problema disso é que o uso da classe FullScreenEvent está na definição do método redrawFullScreen e portanto não é possivel defini-lo somente quando for uma versão ou outra. O jeito então então é mudar a assinatura do método, para ao invés de receber um FullScreenEvent receber um Event. Com essa correção o código ficaria assim:
import flash.events.FullScreenEvent;

public class Main extends MovieClip {

//...

function suportaTelaCheia():Boolean {
//Verifica versao >= que 9.0.28
}

private function onStage(event:Event):void {
if( suportaTelaCheia() ) {
stage.addEventListener( FullScreenEvent.FULL_SCREEN, redrawFullScreen );
}
}

//...

function redrawFullScreen(event:Event):void {
if( event['fullScreen'] ) {
//Posicionar elementos para tela cheia
} else {
//Posicionar elementos para tamanho normal
}
}

//....

}
Repare que além de ter mudado a assinatura do método, mudei também o uso do objeto event. Isso foi feito porque a classe Event não possui o atributo fullScreen declarado, então utilizar event.fullScreen daria erro de compilação. Mas como sabemos que ali há este atributo, podemos utilizá-lo usando a notação event['fullScreen'].

quarta-feira, 14 de maio de 2008

Como instalar o flash 9.0.16 para testar seu swf

É muito importante que ao criar um swf complexo, como um player de vídeo por exemplo, você execute testes em todas as versões de flash player que você deseja que ele suporte. Para isso a Adobe oferece em seu site diversas versões antigas do flash player para download. Contudo, se você está no Windows XP e deseja intalar a versão 9.0.16, mas já possui uma versão mais nova instalada, é preciso seguir os seguintes passos.

Primeiro é preciso desinstalar a versão atual do flash player executando o programa uninstall_flash_player.exe encontrado na página How to uninstall the Adobe Flash Player plug-in and ActiveX control. Depois de executar isso é preciso acessar o regedit e remover a seguinte chave: HKEY_LOCAL_MACHINE\SOFTWARE\Macromedia\FlashPlayer.

Feito isso, reinicie o computador. Ok ok, paciência, estamos lidando com Windows portanto todo cuidado é pouco. Mãos a obra: Iniciar > Reiniciar (Heim?).

Após reiniciado basta instalar a versão 9.0.16 que pode ser baixada em Archived Flash Players available for testing purposes. Nesse link podem ser encontradas diversas outras versões do flash player e é bem possível que o escrito aqui sirva para instalar essas outras versões.

terça-feira, 6 de maio de 2008

Undefined method *_path no uso do form_for

Essa é uma dica boba para aqueles que estão começando em ruby on rails como eu. Fazendo aqui um exemplos simples de cadastro de filmes recebi o seguinte erro:
NoMethodError in Filme#incluir

Showing filme/incluir.html.erb where line #5 raised:

undefined method `filmes_path' for #<ActionView::Base:0xb76f144c>

Extracted source (around line #5):

2:
3: <%= error_messages_for :filme %>
4:
5: <% form_for @filme do |f| %>
6: <p>
7: <b>Título</b><br />
8: <%= f.text_field :titulo %>
Pelo que eu entendi até agora, isso acontece porque eu não estou utilizando os métodos padrões do ruby on rails. Se eu estiver falando besteira por favor me corrijam. O fato é que resolvi isso definindo a ação que o form deverá executar, o código da view então ficaria da seguinte forma:
<h1>Incluir novo filme</h1>

<%= error_messages_for :filme %>

<% form_for @filme, :url => { :action => 'salvar' } do |f| %>
<p>
<b>Título</b><br />
<%= f.text_field :titulo %>
</p>

<p>
<b>Resumo</b><br />
<%= f.text_area :resumo %>
</p>

<p>
<%= f.submit "Incluir" %>
</p>
<% end %>
No caso o controlador possui o método salvar.

quinta-feira, 1 de maio de 2008

Dependência entre tarefas no Scrum

Um problema recorrente nas reuniões diárias do nosso projeto aqui na globo.com é a dependência entre tarefas. Sempre ocorrem interrupções entre o fluxo de "O que eu fiz" e "O que farei" para questionar a algum colega do time se determinada tarefa já foi concluída. No caso afirmativo ele pode então pegar a tarefa que era dependente. Isso acontece frequentemente, e quando ocorre o fluxo desanda e a atenção se dispersa. Começam então algumas conversas paralelas que acabam atrapalhando o andamento da reunião.

O ideal, é claro, é que não houvessem tarefas dependentes entre si em uma história. Mas isso é muito díficil, e segundo minha breve experiência com Scrum, e nesse ponto ela se resume a apenas alguns poucos projetos web, me parece ser impossivel. Como exemplo posso mostrar três tarefas que foram presentes na maioria das histórias dos projetos que participei e que são dependentes entre si:

1- Criar design
2- Criar html e css estático
3- Implementar camada de visualização

A tarefa 2 depende da tarefa 1, e a tarefa 3 depende da tarefa 2. É claro que seria possível paralelizar a tarefa 3 com a 2, fazendo com que a implementação da camada de visualização fosse apenas de um html tosco. Mas mesmo assim seria necessária uma quarta tarefa para fazer o merge do html bonito com a camada de visualização, e esta nova tarefa seria enfim dependente da tarefa 2.

Ou seja, não vejo como paralelizar todas elas. E cabe aqui uma brecha para colaboração dos leitores no caso de terem encontrado alternativas, ficaria feliz de conhecê-las. De qualquer forma, existem outras situações específicas de cada empresa e de cada time que elevam mais ainda o número de dependência entre as tarefas.

Eis então que presenciei em outro time uma boa adaptação da reunião diária. Ao invés de cada membro do time responder as três perguntas, um em seguida do outro, os membros dizem primeiro o que fizeram, uma após o outro, depois dizem o que pretendem fazer, um atrás do outro, e ao final o que os impede, também da mesma forma.

Com isso o time resolveu de maneira simples um problema que parece não ter solução, sem sacrificar a reunião diária, pois todos saem dela sabendo o que fazer, o que já está pronto e quais são os impedimentos do projeto. Cumprindo exatamente o propósito dela.

segunda-feira, 21 de abril de 2008

Demora do Firefox para começar a receber os eventos de um Comet

Quando abrimos um HTTP Stream, ou Comet como é chamado hoje em dia, no Firefox, demora um tempo até que os dados comecem a ficar disponíveis. Ao que parece ele espera haja 1kb de dados já recebidos para começar a deixá-los disponíveis para uso. Contudo, há uma forma de habilitar o processamento dos eventos do Comet no Firefox diretamente sem esperar esse 1kb inicial. Basta definir o content type do retorno como multipart/x-mixed-replace. Em Java seria algo como o mostrado abaixo:
response.setContentType("multipart/x-mixed-replace");

sábado, 19 de abril de 2008

Ativando o conector NIO no Tomcat 6

Para utilizar o CometProcessor do Tomcat 6 é preciso configurar o servidor para utilizar o NIO Connector ao invés do conector http padrão. Caso contrário, ao acessar o seu CometProcessor você receberá o seguinte erro:
message HTTP method GET is not supported by this URL

description The specified HTTP method is not allowed for the requested resource (HTTP method GET is not supported by this URL).
Para habilitar então este conector basta alterar no arquivo server.xml o atributo protocol do conector, deixando-a mais ou menos da seguinte maneira:
    <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" 
connectionTimeout="20000"
redirectPort="8443" />

terça-feira, 15 de abril de 2008

Globo.com no Fisl 9.0

Do dia 17 a 19 de Abril estarei em Porto Alegre participando do Fisl 9.0 junto com Guilherme Chapiewski, Tiago Peczenyj e mais um monte de gente da Globo.com. Estaremos lá para conversar um pouco sobre os problemas e soluções de um dos maiores portais da internet brasileira e todo nosso esforço para basearmos essas soluções em software livre. Especificamente estarei lá para conversar sobre o portal Globo Vídeos e as nossas ultimas pesquisas com software livre. Quem quiser me encontrar para bater um bom papo nerd é só me procurar no stand da Globo.com. É claro que não estarei o tempo inteiro lá, pois assistirei diversas palestras, no entanto farei o possível para ficar o máximo de tempo lá. Além disso criei um grupo de fotos no 8P para publicarmos as fotos do evento. Assim, se você quiser participar do grupo de fotos do Fisl 9.0 é só se associar e publicar suas fotos. Por hora é só, vejo vocês lá!

segunda-feira, 14 de abril de 2008

Bash para redimensionar imagens

Toda segunda-feira é a mesma história, depois de um final de semana de festas e diversão, todo mundo fica solicitando, porque não dizer exigindo as fotos tiradas durante os eventos. O problema é que fotos de câmeras digitais costumam ser grandes, tornando dificil a transferencia para os amigos.

O Caio Moritz postou em seu blog uma dica de como redimensionar imagens pela linha de comando usando o comando mogrify. O problema é que o bash que ele criou redimensiona as imagens sem manter as originais. Então, tomei a liberdade de incrementá-lo um pouco, devido às minhas necessitades. Segue abaixo como ficou:
#!/bin/sh
if [ -z $2 ]
then
echo "Uso: resizepics.sh diretorioFonte diretorioDestino";
exit;
fi

echo "Criando diretorio $2";
mkdir "$2";

echo "Copiando arquivos de $1 para $2";
cp "$1/"*.jpg "$2";

echo "Entrando no diretorio $2";
cd "$2"

echo "Redimensionando imagens";
for img in *.jpg;
do
echo "$img";
mogrify -resize 800x600 "$img";
done
Pronto, salve com o nome de resizepics.sh. Para executá-lo é só informar como primeiro parâmetro o diretório onde estão as fotos, e no segundo o diretório onde serão colocadas as fotos redimensionadas. É claro que para isso funcionar você precisa instalar o pacote imagemagick conforme informado no post do Caio.

sexta-feira, 4 de abril de 2008

Brincando de extensões com o chrome do Firefox

Desenvolver extensões para o Firefox é algo muito divertido. Mas há certas questões que nos deixam de cabelo em pé. Basicamente por falta de exemplos e tutoriais simples. No caso, eu procurava duas coisas: Como acessar o elemento html que foi clicado com o botão direito do mouse, e como copiar um determinado trecho selecionado para a área de transferências (clipboard).

Nessas horas então, nada melhor que analisar os códigos contidos no Chrome do Firefox. Descompactando os arquivos browser.jar e toolkit.jar, localizados no diretório ${FIREFOX_HOME}/chrome, podemos verificar diversos bons exemplos.

No arquivo browser.js do pacote browser.jar por exemplo, pude verificar que para acessar o elemento clicado com o botão direito basta utilizar a notação document.popupNode. O exemplo abaixo mostra de maneira simples um Xul e um Js de um item no menu do botão direito cuja única função é exibir a tag html do elemento clicado com o botão direito.

Xul:
<popup id="contentAreaContextMenu">
<menuitem id="itemMenuExibirTag" label="Exibir tag" oncommand="exibirTag()"/>
</popup>
Js:
function exibirTag() {
alert( document.popupNode.tagName );
}
Já para implementar a função de copiar para a área de transferência, verifiquei no arquivo browser.xul do pacote browser.jar que os menus de copiar do próprio navegador chamavam a função goDoCommand com o parâmetro cmd_copy. Encontrei essa função no arquivo globalOverlay.js do pacote toolkit.jar. Havia nele um comentário informando que a função era apenas para manter compatibilidade com versões antigas, e realmente a única coisa que ela fazia era repassar a função para o método doCommand da classe CommandUpdater. Abaixo mostro um exemplo de item que seleciona todos os itens de uma página e copia o conteúdo para o clipboard:

Xul:
<popup id="contentAreaContextMenu">
<menuitem id="itemMenuCopiarPagina" label="Copiar pagina" oncommand="copiarPagina()"/>
</popup>
Js:
function copiarPagina() {
CommandUpdater.doCommand('cmd_selectAll');
CommandUpdater.doCommand('cmd_copy');
}

quinta-feira, 3 de abril de 2008

Definindo atributos somente leitura em uma classe PHP

O modificador private em PHP, assim como em quase todas as linguagens, impede que membros de uma classe sejam acessados para leitura e escrita. Mas e se você deseja que os atributos de uma classe sejam privados para escrita mas não para leitura? Uma solução simples seria criar métodos get para cada atributo que se deseja liberar a leitura. Mas essa solução é muito verbosa, e como costumo dizer, se você deseja fazer algo desse tipo, melhor então usar Java.

O PHP possui o que no site está descrito como métodos mágicos. No caso para liberarmos a leitura de atributos privados de uma classe, utilizaríamos o método mágico __get. Esse método é chamado sempre que um atributo da classe é acessado. Veja abaixo como ficaria a implementação desse método de forma a liberar todos os atributos da classe:
class Pessoa {

private $nome;
private $idade;

function Pessoa($nome,$idade) {
$this->nome = $nome;
$this->idade = $idade;
}

function __get($atributo) {
echo "<br/>Leu atributo $atributo";
return $this->$atributo;
}
}
Repare que interessante a sintaxe do corpo do método __get. Eu retorno um atributo do objeto atual que contenha o nome enviado por parâmetro. Para verificarmos o funcionamento dele podemos testar com o seguinte código:
$eu = new Pessoa("Tiago",25);
echo "<br/>" . $eu->nome . " tem " . $eu->idade . " anos";
echo "<br/>Atributo nao existente: " . $eu->atributoNaoExistente;
A saída esperada é:
Leu atributo nome
Leu atributo idade
Tiago tem 25 anos
Leu atributo atributoNaoExistente
Atributo nao existente:

terça-feira, 1 de abril de 2008

Mock de classe no PHPUnit executa o construtor

O mock de classes no PHPUnit não funciona muito bem, o ideal, claro é mocar interfaces, mas quando isso não é possível é preciso estar ciente de que o construtor da classe mocada será executado e portanto o teste ficará um tanto falho.

É possivel identificar facilmente esse problema com o código abaixo. No caso mocarei a classe ClasseUtilizada para ser utilizada na classe ClasseASerTestada. Ao executar o teste é possível verifique que o echo dentro do construtor será executado erroneamente.
class ClasseUtilizada {   
function ClasseUtilizada() {
echo "\n[passou no construtor de ClasseUtilizada]\n";
}
public function fazAlgumaCoisa() {
return "uhuuu";
}
}
class ClasseASerTestada {
function ClasseASerTestada($in) {
echo "\n[" . $in->fazAlgumaCoisa() . "]\n";
}
}
class MeuTeste extends PHPUnit_Framework_TestCase {
/**
* @Test
*/
public function teste_com_classe() {
$mock = $this->getMock("ClasseUtilizada");
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(2));
new ClasseASerTestada($mock);
}
}
Uma forma de contornar isso é se aproveitar das características dinâmicas do PHP para simplesmente criar um Mock de uma classe que não existe. Mas pra isso é preciso informar no método getMock não só o nome de uma classe inexistente, como também a lista de métodos que a classe teria. Veja como ficaria abaixo:
    /**
* @Test
*/
public function teste_com_classe_inexistente_indicando_metodos() {
$mock = $this->getMock("ClasseUtilizadaMock", Array('fazAlgumaCoisa'));
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(4));
new ClasseASerTestada($mock);
}
Se por um acaso você não indicar o segundo parâmetro, que é um array com os métodos que a classe possui, um erro parecido com este será exibido: "Fatal error: Call to undefined method Mock_ClasseUtilizadaMock_4322e2fd :: fazAlgumaCoisa()".

O problema dessa solução é o caso em que a classe a ser utilizada possui verificação de tipo no recebimento dos parâmetros. Como a classe mock criada dinamicamente não é do tipo esperado, ocorrerá um erro. Veja um exemplo de como isso aconteceria abaixo:
class ClasseUtilizada {   
function ClasseUtilizada() {
echo "\n---->passou no construtor de ClasseUtilizada<-----\n";
}
public function fazAlgumaCoisa() {
return "uhuuu";
}
}
class ClasseASerTestadaComVerificacaoDeTipo {
function ClasseASerTestada(ClasseUtilizada $in) {
echo "\n[" . $in->fazAlgumaCoisa() . "]\n";
}
}
class MeuTeste extends PHPUnit_Framework_TestCase {
/**
* @Test
*/
public function teste_com_classe_inexistente_na_classe_com_verificacao_de_tipo() {
$mock = $this->getMock("ClasseUtilizadaMock", Array('fazAlgumaCoisa'));
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(5));
new ClasseASerTestadaComVerificacaoDeTipo($mock);
}
}
Nesse caso o erro exibido será "Argument 1 passed to ClasseASerTestadaComVerificacaoDeParametro :: ClasseASerTestadaComVerificacaoDeParametro() must be an instance of ClasseUtilizada"

Para esses casos não tem jeito, é preciso alterar as classes para que exista uma interface a ser mocada. Mas eu sugiro que não utilize a verificação de tipo nos parâmetros, caso contrário, seria melhor deixar de lado uma linguagem dinâmica como PHP e usar uma mais estática como Java.

quarta-feira, 19 de março de 2008

GreaseMonkey para feed do InfoBlogs no GoogleReader

Quem assina o feed do InfoBlogs deve ter o mesmo problema que eu. O link deste feed não é para o post em si, ele é para uma cópia do post na página do próprio InfoBlogs. Particularmente acho isso irritante, pois só costumo entrar no blog do autor quando o post é interessante e desejo comentá-lo. Nesse caso, preciso aguardar o carregamento (que é bem lento) da página do InfoBlogs para só depois poder clicar e ver o post original, onde eu posso comentar.

Para resolver esse problema implementei um script no GreaseMonkey para remontar a página do GoogleReader com os links corretos. Para quem não sabe o GreaseMonkey é uma extensão do Firefox, o melhor navegador existente na atualidade, que permite ao usuário configurar scripts para serem rodados ao término do carregamento de uma determinada página. Eu falei brevemente dele no post em que falo sobre as extensões do Firefox essenciais para um desenvolvedor web, e agora com este exemplo ficará mais fácil entender como ele funciona.

O código do script encontra-se abaixo. O que ele faz é verificar a cada 3 segundos se houve alguma alteração de estado no GoogleReader. Para então mudar os links caso haja necessidade. Só há uma ressalva, ele não funciona para o modo de visualização de lista, só a visão expandida. O motivo é que não gosto da visualização em lista e portanto não me interessei em testar nela :P
// ==UserScript==
// @name GoogleReaderGM
// @namespace http://www.google.com/reader
// @include http://www.google.com/reader/*
// ==/UserScript==

function GoogleReaderGM() {}
GoogleReaderGM.prototype = {
isChanged: function() {
var e = document.getElementById("entries");
var ds = e.getElementsByTagName("div");
if( this._oldUrl != window.location.href ) {
this._oldUrl = window.location.href;
this._oldQtds = ds.length;
return true;
}
if( this._oldQtds != ds.length ) {
this._oldQtds = ds.length;
return true;
}
return false;
},
mudaLinksInfoBlogs: function() {
if( this.isChanged() ) {
var e = document.getElementById("entries");
var as = e.getElementsByTagName("a");
for( i=0; i<as.length; i++ ) {
var a = as[i];
if( a.className=="entry-title-link" && a.href.indexOf("www.infoblogs.com.br/view.action")!=-1 ) {
a.href = a.href.replace("view","redir/go");
}
}
}
}
}

var gr = new GoogleReaderGM();

setInterval(function() { gr.mudaLinksInfoBlogs(); }, 3000);
Como pode ser visto eu defino como um comentário JavaScript em quais páginas esse script será executado. No caso, todas aquelas abaixo de http://www.google.com/reader.

O interessante é que se você quiser pode utilizar esse código como template para executar outras funcionalidades no GoogleReader. Basta adicionar mais alguma função, e utilizando isChanged você fica sabendo se alguma entrada nova apareceu na área que exibe as entradas dos feeds. Isso é claro se você utilizar a visão expandida (Expanded view), pois o método isChanged só foi feito pra ela.

quinta-feira, 13 de março de 2008

Escapando textos em funções de javascript executadas em links

Para determinar a execução de uma função javascript no clique de um link temos duas possibilidades. Ou utilizamos o próprio atributo href da tag a com o protocolo javascript: ou adicionamos o atributo onclick fazendo a chamada à função.

Contudo o comportamento de ambos não é totalmente igual. Principalmente quando chamamos funções que passam como argumento algum texto escapado com a função escape. Normalmente usa-se esse escape para evitar error de javascript devido ao uso de aspas simples e duplas nos textos.

Veja abaixo dois exemplos de cógigo com o erro por não usar escape. O primeiro com aspas simples, que faz com que o javascript ache que o texto terminou antes do que deveria e o segundo com aspas duplas fazendo o navegador acreditar que o atributo terminou:
var textoComAspaSimples = "Quero um copo d'água";
var s = '<a href="javascript:alert(\'' + textoComAspaSimples+ '\')">Clique aqui</a>';
document.writeln(s);

var textoComAspaDupla = 'Você está com "sede"?';
var s = '<a href="javascript:void(0)" onclick="alert(\'' + textoComAspaDupla+ '\')">Clique aqui</a>';
document.writeln(s);
Para evitar esses erros uma boa idéia é utilizar a função escape. No entanto isso não funciona direito por href. Neste atributo o navegador interpreta o código escapado causando o mesmo erro. Isso pode ser observado no exemplo a seguir, no primeiro link gerado o erro acontece, no segundo não:
var textoComAspaSimples = "Quero um copo d'água";
var s = '<a href="javascript:alert(\'' + escape(textoComAspaSimples) + '\')">Clique aqui href</a><br/>';
s += '<a href="javascript:void(0)" onclick="alert(\'' + escape(textoComAspaSimples) + '\')">Clique aqui onclick</a>';
document.writeln(s);
O problema é que com essa solução a função que recebe este parâmetro precisa executar uma transformação reversa utilizando a função unescape. Caso contrário veremos algo parecido com o que o segundo link do exemplo acima mostra: Quero%20um%20copo%20d%27%C3%A1gua.

Implementando a transformação reversa na função que recebe teríamos algo como o mostrado abaixo. Uma nova função que recebe o parâmetro escapado e o transforma para utilizá-lo:
function escapedAlert(s) {
alert( unescape(s) );
}
var textoTodoErrado = "Sentes \"sede\"?\nQuero um copo d'água";
var s = '<a href="javascript:void(0)" onclick="escapedAlert(\'' + escape(textoTodoErrado) + '\')">Clique aqui onclick</a>';
document.writeln(s);
Mas isso ainda não é o ideal pois continuaríamos sem poder utilizar a função dentro do atributo href, e além disso precisaríamos sempre preparar as funções para receber parâmetros escapados. Uma outra alternativa é criar nossa própria função de escape. Como é mostrado no exemplo abaixo:
function escapeForLink(s) {
if(s) {
var r = s.split("\n").join("\\n");
r = r.split("\r").join("\\r");
r = r.split("'").join("\\u0027");
r = r.split('"').join("\\u0022");
return r;
}
return s;
}
var textoTodoErrado = "Sentes \"sede\"?\nQuero um copo d'água";
var s = '<a href="javascript:alert(\'' + escapeForLink(textoTodoErrado) + '\')">Clique aqui href</a><br/>';
s += '<a href="javascript:void(0)" onclick="alert(\'' + escapeForLink(textoTodoErrado) + '\')">Clique aqui onclick</a>';
document.writeln(s);
Com essa função os problemas de escape para passagem de parâmetro de texto em funções executadas apartir de links fica resolvido.

quarta-feira, 27 de fevereiro de 2008

Component do VRaptor só pode ter um construtor público

Nem tudo são flores com o framework MVC VRaptor. Uma classe anotada com Component só pode ter um construtor público. Senão o seguinte erro é exibido na inicialização do container web:
org.vraptor.config.ConfigException: cannot load classes
...
Caused by: org.vraptor.component.InvalidComponentException:
com.globo.ekzameno.vraptor.QuestionarioLogic component has 2 accessible constructors.
This is not desirable as it may reflect optional arguments and create complex attributes.
Ok, ok, isso não é o fim do mundo para o VRaptor, mas me atrapalhou um pouco com os testes unitários, pois eu pretendia fazer injeção de dependência por construtor. Como esse framework não me permite isso, terei que fazer por método set. Ou seja, ao invés de fazer assim:
public TiagoLogic() {
this(new RepositorioImpl());
}
public TiagoLogic(Repositorio repositorio) {
this.repositorio = repositorio;
}
Terei que fazer assim:
public TiagoLogic() {
setRepositorio(new RepositorioImpl());
}
public void setRepositorio(Repositorio repositorio) {
this.repositorio = repositorio;
}
Não é um grande problema, só questão de preferência mesmo. Prefiro por construtor.

NoClassDefFoundError de Expectations no VRaptor 2


Atenção, não consegui mais replicar o problema descrito neste post. Provavelmente a necessidade da classe não era do VRaptor.


Comecei a utilizar o VRaptor em um projeto pessoal e na primeira tentativa obtive um erro que foi simples corrigir, mas que achei pertinente postar aqui porque não encontrei no google ninguém com o mesmo problema e porque a documentação deste framework não informava nada sobre.

O erro que obtive foi o seguinte:
javax.servlet.ServletException: Servlet.init() for servlet vraptor2 threw exception
Cuja causa foi:
java.lang.NoClassDefFoundError: org/jmock/Expectations
Para resolver isso bastou incluir no classpath da aplicação o jar do JMock. Esse jar não é incluso no blank-project oferecido no site do VRaptor, por isso acho que outras pessoas poderão passar pelo mesmo problema.

terça-feira, 26 de fevereiro de 2008

Problema do gets com uso de parametros na linha de comando em ruby

Brincando um pouco com ruby me deparei com um problema que parecia um pouco exotérico. Utilizando a função gets num script que recebe parâmetros por linha de comando, o seguinte erro era exibido:
tiago@tiago-laptop:~/Projetos/testeRuby$ ruby Pessoa.rb Tiago
Pessoa.rb:14:in `gets': No such file or directory - Tiago (Errno::ENOENT)
Esse mesmo erro não ocorria quando não era enviado nenhum argumento na linha de comando. Pesquisando sobre o assunto no Google, encontrei uma discussão que clareou minhas idéias. O Ruby herda o comportamento da função gets do Perl, ou seja, se houver um parâmetro na linha de comando, essa função assume que este parâmetro é o path de um arquivo e portanto tenta abrí-lo.

Para resolver essa questão basta deixar claro que se deseja que a função gets recupere apenas o texto digitado. Na verdade ao invés de usarmos gets puro devemos usar o do STDIN da seguinte maneira STDIN.gets.

quarta-feira, 30 de janeiro de 2008

Tela cheia em flash no Globo Vídeos Player

Subiu agora pouco uma nova versão do Globo Vídeos contendo diversas melhorias e algumas correções pontuais. A maioria dessas alterações poucos irão perceber, pois são problemas que somente o controle de qualidade interno tem a paciência de procurar. Mais uma das melhorias sem duvida será muito bem notada, o botão de tela cheia.

Na versão anterior, que usava Windows Media Player, a funcionalidade de tela cheia existia para os usuários de Internet Explorer. Quando entrou o player em flash ela foi removida. Portanto muitos usuários reclamaram de sua remoção.

Acontece que fazer o full screen no flash não é uma tarefa trivial devido as diversas versões de flash que teríamos que suportar. Assim preferimos implementar uma versão mínima do player antes do Big Brother, para posteriormente implementar essas pequenas melhorias. Se tivessemos optado por lançar já com um player completo, talvez só pudéssemos entregá-lo ao final do Big Brother.

Eu particularmente acredito que tomamos a decisão correta. Apesar de termos perdido por um mês a funcionalidade de tela cheia, milhares de usuários que não conseguiam ver vídeos, por causa da M que é o Windows Media Player, agora conseguem. E melhor ainda, agora também têm o botãozinho de tela cheia.

segunda-feira, 14 de janeiro de 2008

Repassando parâmetros rest para funções que esperam parâmetros rest

No ActionScript 3 existe um parâmetro especial, comumente chamando de parâmetro rest, que permite que uma função receba "infinitos" parâmetros. O Java, apartir da versão 5, também possui essa facilidade, que é chamada de varargs. Veja abaixo o exemplo de uma função e o logo abaixo o uso dela:
//Funcao que mostra todos os parametros
public function mostra(... numeros) {
if( numeros && numeros.length > 0 ) {
for each( var i in numeros ) {
trace(i);
}
}
}
//Uso da funcao:
mostra(1);
mostra(1,2,3);
mostra(1,2,3,5,8);
Apesar de ser um recurso muito bom, a forma como ela foi implementada no ActionScript nos traz um problema no momento em que precisamos repassar esses parâmetros para uma outra função que também receba parâmetros opcionais rest.

Isso acontece porque quando a primeira função é chamada, ela é informada com os parâmetros separados por vírgula, mas ela os recebe como um array. Então se você enviar este array para o segundo método, ele será considerado apenas um parâmetro. No Java há um tratamento mais esperto, de forma que quando enviado um array o compilador não permite que mais nenhum parâmetro seja colocado na chamada à função.

Para ilustrar melhor vamos pensar num cenário que mostrei no post passado sobre como testar funções que executem javascript. No caso era uma função proxy para isolarmos a execução de ExternalInterface.call:
public function executarJavaScript(funcao:String, ... parametros) {
try {
return ExternalInterface.call(funcao,parametros);
} catch(erro:Error) {
return null;
}
}
No caso, a função call de ExternalInterface também espera receber parâmetros opcionais, mas como estamos mandando um array, ele achará que estamos mandando apenas dois parâmetros, um com o nome da função javascript e outro com um array. No Google é fácil encontrar uma solução POG e muito feia para isso. Nada mais é que uma cadeia megazord de ifs:
public function executarJavaScript(funcao:String, ... p) {
try {
if( p.length == 0 ) return ExternalInterface.call(funcao,null);
else if( p.length == 1 ) return ExternalInterface.call(funcao,p[0]);
else if( p.length == 2 ) return ExternalInterface.call(funcao,p[0],p[1]);
else if( p.length == 3 ) return ExternalInterface.call(funcao,p[0],p[1],p[2]);
else if( p.length == 4 ) return ExternalInterface.call(funcao,p[0],p[1],p[2],p[3]);
} catch(erro:Error) {
return null;
}
}
Ô coisa feia!

A solução mais elegante para esse problema é utilizar os recurso de reflexão do ActionScript. Ou seja, vamos utilizar o método apply da classe Function, que aguarda os parâmetros como um array, exatamente como recebemos na função ao utilizar um parâmetro rest. Veja como ficaria:
public function executarJavaScript(funcao:String, ... parametros) {
try {
var f:Function = ExternalInterface.call;
var parametrosEnviar:Array = new Array(funcao);
parametrosEnviar = parametrosEnviar.concat(parametros);
return f.apply(null,parametrosEnviar);
} catch(erro:Error) {
return null;
}
}
No caso, concatenamos num array de parâmetros a serem enviados a função e os parâmetros rest. Ao enviá-los pela função apply eles são enviados como se fossem diversos parâmetros separados por vírgula e o método call os recebe corretamente como parâmetros rest.

domingo, 13 de janeiro de 2008

Como testar funções que fazem chamadas javascript no actionscript?

No ActionScript é possível chamar funções javascript utilizando a função ExternalInterface.call. O problema é que essa função é estática, dificultando muito a criação de testes unitários para as funções que necessitam acessar esses recursos externos.

Para fazer isso é preciso isolar a chamada ao javascript em uma função, e no teste unitário substituir essa função por uma outra que apenas a substitua por uma comportamento esperado de acordo com o teste. Vamos ver como fazer isso:

Primeiro isolamos a chamada a ExternalInterface.call em um função que serviria apenas como proxy. Em linguagens menos dinâmicas precisaríamos isolar essa função em uma outra classe pois não seria possível substitui-la posteriormente:
public function executarJavaScript(funcao:String,parametros:Object = null) {
try {
return ExternalInterface.call(funcao,parametros);
} catch(erro:Error) {
return null;
}
}
A classe então que possui essa função precisa ser dinâmica, ou seja deve ter um modificador dynamic, e possuir um atributo da classe Function que recebe no construtor essa função que foi criada:
dynamic public class MinhaClasse {
public var js:Function;
function MinhaClasse() {
js = executarJavaScript;
}
public function executarJavaScript(funcao:String,parametros:Object = null) {
//Conteudo da função já exibido acima :P
}
//Outras funções
}
As demais funções da classe deverão usar então o atributo js como uma função para executar o recurso externo desejado. Veja um exemplo que recupera do javascript a URL onde o swf está sendo exibido:
public function obterUrlAtual():String {
try {
var objJs = js("document.location.href.toString");
if( !objJs ) objJs = js("document.location.toString");
if( !objJs ) return null;
return new String(objJs);
} catch(e:Error) {
return null;
}
}
Para criar um teste unitário com o ASUnit para esta função de exemplo basta configurar de diversas formas o atributo js da classe MinhaClasse de acordo com o comportamento que você deseja testar. Um exemplo é exibido abaixo:
public function testSegundFuncaoJavaScriptEhChamada():void {
var m:MinhaClasse = new MinhaClasse();
m.js = function(funcao:String,parametros:Object = null) {
if(funcao=="document.location.toString") {
return "http://programandosemcafeina.blogspot.com";
} else {
return null;
}
}
var s:String = m.obterUrlAtual();
assertEquals("Quando a primeira função retorna null a segunda função deve ser chamada","http://programandosemcafeina.blogspot.com",s);
}

segunda-feira, 7 de janeiro de 2008

Checar versão do flash de dentro do SWF

Descobrir a versão do flash por javascript é moleza. Uma rápida busca no google lhe traz dezenas de script prontos pra usar. Mas verificar a versão do flash de dentro do SWF foi díficil de encontrar. Até é bem simples. Basta usar o atributo estático version da classe flash.system.Capabilities. Assim:
trace("versao: " + Capabilities.version);
Simples, porém foi díficil encontrar. Como esse código só funciona no Action Script 3, fica parecendo que ele não tem muita utilidade. Mas acontece que existem pequenas diferenças entre as chamadas minor revisions que precisam ser verificadas no projeto em que estou trabalhando.

quarta-feira, 2 de janeiro de 2008

Redirecionar requisições do Apache para uma imagem

Para redirecionar todas as requisições do apache para uma determinada imagem basta utilizar o módulo mod_rewrite.
Se você ainda não utiliza este módulo do apache, é preciso descomentar no arquivo httpd.conf o carregamento dele:
LoadModule rewrite_module modules/mod_rewrite.so
Em seguida deve-se ativar o módulo e criar uma regra, também alterando o mesmo arquivo:
RewriteEngine On
RewriteRule /* /empty.gif
Neste caso o comando RewriteRule informa que todas as requisições devem ser atendidas pelo recurso /empty.gif.