Mensagens dinâmicas com MessageFormat

Esta é uma rápida dica sobre uma classe muito útil da API Java, a classe MessageFormat. Não vou dar muitas explicações, e mais detalhes podem ser encontrados na própria API: http://download.oracle.com/javase/1.4.2/docs/api/java/text/MessageFormat.html

Dentre as muitas coisas legais que esta classe permite fazer (formatação de valores monetários, data, hora), temos uma maneira de tratar mensagens internacionalizadas (ou não) cujo conteúdo muda de acordo com o valor específico de um parâmetro. Explico melhor:

Digamos que você precise internacionalizar, ou apenas armazenar em um properties, a seguinte frase:

“Duke, você nos visitou uma vez.”

Digamos que você precise tratar esta frase, de maneira que quando a quantidade de visitas for maior, ela mude para:

“Duke, você nos visitou (n) vezes”

Certo! O tal (n) alí pode ser qualquer número, então o que muita gente faz são concatenações para conseguir o resultado esperado, por exemplo:

“Duke, você nos visitou” + n + “vezes”;

Vamos Piorar a situação? E se o Duke nunca lhe visitou, ou seja, n é igual a zero? E é lógico que não é somente o Duke que lhe visita, então você ainda tem o seguinte:

m*, você nos visitou uma vez. (quando n = 1)

m* + você nos visitou + n + vezes. (quando n > 1)

m*, você nunca nos visitou. (quando n = 0)

*onde m pode ser o Duke ou qualquer pessoa.

Isto ainda pode ficar mais complicado quando você precisa passar a mensagem para mais de um idioma, pois a posição das partes variantes (m e n) pode ser diferente na mensagem. Este cenário é comum em sistemas internacionalizados.

Apesar de a maioria dos frameworks web com foco em componentes de apresentação apresentarem algum suporte a este tipo de situação, algumas vezes será necessário tratar mensagens assim no código Java.

Para estas situações a classe MessageFormat é ideal.

Em primeiro lugar, podemos ter apenas uma mensagem que trata todas as possibilidades, acredite. Basta colocarmos, para cada parte variável da mensagem, um placeholder, que identificará a presença de um valor dinâmico. Como no exemplo temos dois valores variáveis (n e m) teremos dois placeholders na mensagem. A sintaxe de um placeholder é um numérico entre chaves: {0} e {1}. Os valores indicam a posição dos parâmetros passados para o método que formatará a mensagem.

Nossa mensagem então ficaria assim:

{1}, você nos visitou {0} vezes.

Mas espere! Isto ainda não nos atende. Apenas uma das variações da mensagem está contemplada; precisamos do tipo de formato choice: este tipo de formato nos permite escolher dinamicamente uma parte da mensagem de acordo com o valor passado a um placeholder:

{1}, você {0,choice,0# nunca nos visitou.|1# nos visitou uma vez.|1<nos visitou {0,number,integer} vezes}.

Na mensagem acima, está em destaque a sintaxe do tipo de formatação choice; ele é colocado após o número do placeholder, depois de uma vírgula.

Em vermelho está o texto que será apresentado caso o valor passado ao placeholder seja zero. Em verde está o texto que será apresentado caso o valor passado ao placeholder seja um. Em azul está o texto que será apresentado caso o valor passado ao placeholder seja maior que um.

OK, mas como fazer a mágica da transformação? Basta passar a mensagem e os parâmetros para o método format, da classe MessageFormat. Abaixo coloco uma classe de exemplo, que mostra ainda a mesma mensagem traduzida em inglês e espanhol, para demonstrar que a localização dos placeholders pode mudar sem afetar o resultado esperado.


import java.text.MessageFormat;

public class FormatacaoMensagem{

private static String ingles = "You have {0,choice,0#never visited us|1#visited us once|1<visited us {0,number,integer} times}, {1}.";
private static String portugues = "{1}, você {0,choice,0#nunca nos visitou.|1#nos visitou uma vez.|1<nos visitou {0,number,integer} vezes}.";
private static String espanhol = "Usted {0,choice,0#nunca nos ha visitado, {1}|1#nos ha visitado una vez, {1}|1<nos ha visitado {0,number,integer} veces, {1}}.";

public static void main(String[] args){

Object[] nenhum = {
new Integer(0),
"Duke"

};

Object[] um = {
new Integer(1),
"Duke"

};

Object[] varios = {
new Integer(2),
"Duke"

};

System.out.println( MessageFormat.format(ingles, nenhum) );
System.out.println( MessageFormat.format(ingles, um) );
System.out.println( MessageFormat.format(ingles, varios) );

System.out.println( MessageFormat.format(portugues, nenhum) );
System.out.println( MessageFormat.format(portugues, um) );
System.out.println( MessageFormat.format(portugues, varios) );

System.out.println( MessageFormat.format(espanhol, nenhum) );
System.out.println( MessageFormat.format(espanhol, um) );
System.out.println( MessageFormat.format(espanhol, varios) );

}
}

Eis o resultado final:

Vale a pena conferir a API para as outras opções que a classe oferece.

Está dada a dica!

Anúncios

Swing e Pivot

Este post é uma rápida nota sobre uma combinação que eu acredito que será muito útil para a criação de aplicações desktop.

Trata-se da combinação de Swing com o Pivot. Isto será possível a partir da versão 2.0.

A imagem abaixo mostra um exemplo, e em seguida coloco o link do exemplo, e de onde é possível obter os fontes:

 

Link do exemplo:

http://ixnay.biz/pivot-demos/swing-demo.html

 

URL  para obter o fonte:

http://svn.apache.org/repos/asf/pivot/trunk/demos/src/org/apache/pivot/demos/swing/

ERRO: detached entity passed to persist

Durante o desenvolvendo um projeto pessoal me deparei com este erro, que indicava que uma entidade
detached estava sendo passada para um método de inclusão:

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: model.Criterio

O método em questão salvava, na verdade, uma entidade de outra classe:

Chave chave = new Chave();
chave.setNome(nomeChave);
chave.setPeso(new BigDecimal(pesoChave));
chave.setCriterioCollection(new ArrayList<Criterio>()); 

for(Criterio c : getCriteriosChave()){
 c.setCodChave(chave);
 chave.getCriterioCollection().add(c);
}

getBusinessDelegate().cadastrar(chave);

Tudo indicava que as entidades da classe Criterio, passadas à coleção da classe sendo salva, estavam vindo do banco de dados,
através de um gerenciador de persistência, mas não era este o caso. As entidades retornadas pelo método getCriteriosChave(),
neste caso, são criadas no momento da população da tela, ou seja, não são retornadas do banco.
Durante algum tempo bati cabeça, tentando entender o porquê deste erro, até que em um post encontrei uma dica que foi valiosa:
o identificador do objeto em questão, estava associado a um componente de tela:

<rich:column>
 <h:inputHidden id="hdncodigoCriterio" value="#{criterio.codigo}"/>
 <h:inputText id="txtNomeChave" required="true"
 disabled="#{!configuracaoGuiaBean.habilitarModificacoesChave}" 
 value="#{criterio.descricao}" size="100" maxlength="60">
 <f:validateLength minimum="5" maximum="60"/>
 </h:inputText>
 </rich:column>

Como isso pode influenciar no erro?
Ao associar o identificador da entidade a um componente de tela, o valor do mesmo, quando não atribuído explicitamente, será zero, e não nulo.
O gerenciador de persistência entenderá esse valor como sendo um identificador válido, como se a entidade tivesse sido obtida do banco de dados.
Para resolver o problema, bastou fazer um teste antes do momento da atribuição: se o valor do identificador for zero, o que indica
uma nova entidade, atribui-se nulo para seu valor:


for(Criterio c : getCriteriosChave()){
 c.setCodigo(c.getCodigo() == 0 ? null : c.getCodigo());
 c.setCodChave(chave);
 chave.getCriterioCollection().add(c);
}

Está dada a dica!

Acessando arquivos xml e dtd de dentro de um jar

Quando se desenvolve uma aplicação empacotada em um jar, e recursos que devem ser acessados pela aplicação encontram-se dentro do mesmo jar (como arquivos xml ou imagens, por exemplo), pegar uma referência para esses recursos pode ser um tanto complicado.

Devo admitir que quebrei a cabeça para conseguir a referência para um arquivo xml dentro de uma aplicação. Tratava-se na verdade de um caso de parse de um XML através de DOM, e este mesmo XML referenciava um DTD, que se encontrava junto do XML.

O problema é que o parse conseguia acessar o XML, mas se perdia ao tentar encontrar o DTD que o validava. A solução encontrada foi passar a URI do arquivo XML, pois dessa forma o parser consegue buscar o DTD no mesmo caminho.

Passar um InputStream não adianta muito, pois streams não dizem nada a respeito de onde estava o recurso.

Eis um trecho de exemplo, espero que ajude:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
URL xmlFileURL = LanguagesIdentifier.class.getResource("arquivo.xml");
Document doc = docBuilder.parse(xmlFileURL.toURI().toString());

Detalhe: tanto o XML quanto o DTD se encontravam no mesmo local que o arquivo .java que os acessava

[]’s

Expressão Regular para reconhecimento de comentários e strings

O código abaixo foi adaptado de um código publicado no artigo Regular Expressions and the Java Programming Language . No original são reconhecidos apenas comentários de uma linha.

A adaptação feita permite reconhecer comentários de multiplas linhas e Strings.


/*
* Prints out the comments found in a .java file.
*/
import java.util.regex.*;
import java.io.*;
import java.nio.*;
import java.nio.charset.*;
import java.nio.channels.*;

public class CharBufferExample {

    public static void main(String[] args) throws Exception {

        // Create a pattern to match comments
        Pattern pInline =
        Pattern.compile(”//.*$”, Pattern.MULTILINE);

        Pattern pMultiline =
        Pattern.compile(”\/{1}\*{1,}.*\*{1,}\/{1}”);

        Pattern pString =
        Pattern.compile(””{1}.*?”{1}”);

        // Get a Channel for the source file
        File f = new File(”nome_do_arquivo.java”);
        FileInputStream fis = new FileInputStream(f);
        FileChannel fc = fis.getChannel();

        // Get a CharBuffer from the source file
        ByteBuffer bb =
        fc.map(FileChannel.MapMode.READ_ONLY, 0, (int)fc.size());
        Charset cs = Charset.forName(”8859_1″);
        CharsetDecoder cd = cs.newDecoder();
        CharBuffer cb = cd.decode(bb);

        // Run some matches
        Matcher m = pInline.matcher(cb);
        while (m.find())
        System.out.println(”Found comment: “+m.group());

        // Run some matches
        Matcher multiline = pMultiline.matcher(cb);
        while (multiline.find())
        System.out.println(”Found comment: n”+multiline.group());

        // Run some matches
        Matcher strings = pString.matcher(cb);
        while (strings.find())
        System.out.println(”Found String: n”+strings.group());
    }
}

Referências sobre expressões regulares em Java:
http://java.sun.com/developer/technicalArticles/releases/1.4regex/
http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/Pattern.html
http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/Matcher.html
http://www.regular-expressions.info/java.html

Código Javascript para Máscaras (Atualizado)

Pois bem, tive que atualizar o código Javascript de máscaras para levar em conta a diferenciação de letras e números, sendo possível definir uma máscara como, por exemplo, LLL###-LL## (onde ‘L’ indica Letras e ‘#’ indica dígitos).

Segue o código atualizado:


// [dFilter] - A Numerical Input Mask for JavaScript
// Written By Dwayne Forehand - March 27th, 2003
// Please reuse & redistribute while keeping this notice.

var dFilterStep

function dFilterStrip (dFilterTemp, dFilterMask){

    dFilterMask = replace(dFilterMask,’#',”);
    dFilterMask = replace(dFilterMask,’L',”);

    for (dFilterStep = 0; dFilterStep < dFilterMask.length++; dFilterStep++){
        dFilterTemp = replace(dFilterTemp,dFilterMask.substring(dFilterStep,dFilterStep+1),”);
    }
    return dFilterTemp;
}

function dFilterMax (dFilterMask){

    dFilterTemp = dFilterMask;

    for (dFilterStep = 0; dFilterStep < (dFilterMask.length+1); dFilterStep++){
        if ((dFilterMask.charAt(dFilterStep)!=’#') && (dFilterMask.charAt(dFilterStep)!=’L')){
            dFilterTemp = replace(dFilterTemp,dFilterMask.charAt(dFilterStep),”);
        }
    }
    return dFilterTemp.length;
}

function dFilter (e, textbox, dFilterMask)
{
    /*
    * KEYS
    *
    * 9 - TAB
    * 8 - BACKSPACE
    *
    */

    var key;
    var strippedMask;

    if(window.event) // IE
    {
        key = window.event.keyCode;
    }
    else if(e.which) // Netscape/Firefox/Opera
    {
        key = e.which;
    }

    dFilterNum = dFilterStrip(textbox.value, dFilterMask);
    strippedMask = dFilterStrip(dFilterMask, dFilterMask);

    if (key!=9 && key!=8 && key!=13){

        if( strippedMask.charAt(dFilterNum.length) == ‘#’){
            if(!( ( key>47&&key<58 ) || ( key>95&&key<106 ) )){
                alert(’Digitar um Número!’);
                key = ‘*’;
            }
        }

        else if(strippedMask.charAt(dFilterNum.length) == ‘L’){
            if(!(key>64 && key<91)){
                alert(’Digitar uma Letra!’);
                key = ‘*’;
            }
        }
    }

    if (key==9){
        return true;
    }
    else if (key==8 && dFilterNum.length!=0){
        dFilterNum = dFilterNum.substring(0,dFilterNum.length-1);
    }
    else if((key>64&&key<91) && dFilterNum.length<dFilterMax(dFilterMask) ){
        dFilterNum=dFilterNum+String.fromCharCode(key);
    }
    else if ( ((key>47&&key<58)||(key>95&&key<106)) && dFilterNum.length<dFilterMax(dFilterMask) ){

        if( key>47&&key<58 ){
            dFilterNum=dFilterNum+String.fromCharCode(key);
        }

        if( key>95&&key<106 ){

            var aux = 0;
            switch(key){
                case 96:
                aux = 48;
                break;
                case 97:
                aux = 49;
                break;
                case 98:
                aux = 50;
                break;
                case 99:
                aux = 51;
                break;
                case 100:
                aux = 52;
                break;
                case 101:
                aux = 53;
                break;
                case 102:
                aux = 54;
                break;
                case 103:
                aux = 55;
                break;
                case 104:
                aux = 56;
                break;
                case 105:
                aux = 57;
                break;
            }

            dFilterNum=dFilterNum+String.fromCharCode(aux);
        }
    }

    var dFilterFinal=”;

    for (dFilterStep = 0; dFilterStep < dFilterMask.length; dFilterStep++)
    {
        if (dFilterMask.charAt(dFilterStep)==’#’ || dFilterMask.charAt(dFilterStep)==’L')
        {
            if (dFilterNum.length!=0)
            {
                dFilterFinal = dFilterFinal + dFilterNum.charAt(0);
                dFilterNum = dFilterNum.substring(1,dFilterNum.length);
            }
            else
            {
                dFilterFinal = dFilterFinal + “”;
            }
        }
        else
        {
            dFilterFinal = dFilterFinal + dFilterMask.charAt(dFilterStep);
        }
        // dFilterTemp = replace(dFilterTemp,dFilterMask.substring(dFilterStep,dFilterStep+1),”);
    }

    textbox.value = dFilterFinal;
    return false;
}

function replace(fullString,text,by) {

    // Replaces text with by in string
    var strLength = fullString.length, txtLength = text.length;

    if ((strLength == 0) || (txtLength == 0))
        return fullString;

    var i = fullString.indexOf(text);

    if ((!i) && (text != fullString.substring(0,txtLength)))
        return fullString;

    if (i == -1)
        return fullString;

    var newstr = fullString.substring(0,i) + by;

    if (i+txtLength < strLength)
        newstr += replace(fullString.substring(i+txtLength,strLength),text,by);

    return newstr;
}

&#91;/sourcecode&#93;

E eis uma página de exemplo:

&#91;sourcecode language="html"&#93;

<HTML>

<HEAD>

<!-- This script and many more are available free online at -->
<!-- The JavaScript Source!! http://javascript.internet.com -->

<script type='text/javascript' src='dFilter.js'></script>
</script>

</HEAD>

<!-- STEP TWO: Copy this code into the BODY of your HTML document  -->

<BODY>

<!-- This script and many more are available free online at -->
<!-- The JavaScript Source!! http://javascript.internet.com -->

<form name="fred" action="fred.htm" method="post">
<table>
<tr>
<td>SSN:</td>
<td><input value="" type="text" onKeyDown="javascript:return dFilter (event, this, 'LLL##-####/##');" style="font-family:verdana;font-size:10pt;width:110px;"></td>
</tr>
<tr>
<td>Phone:</td>
<td><input value="" type="text" onKeyDown="javascript:return dFilter (event, this, '(LLL) ###-####');" style="font-family:verdana;font-size:10pt;width:110px;"></td>
</tr>
<tr>
<td>Zip:</td>
<td><input value="" type="text" onKeyDown="javascript:return dFilter (event, this, 'LLLLL-####');" style="font-family:verdana;font-size:10pt;width:110px;"></td>
</tr>
<td>Data:</td>
<td><input value="" type="text" onKeyDown="javascript:return dFilter (event, this, '###,##');" style="font-family:verdana;font-size:10pt;width:110px;"></td>
</tr>
</table>
</form>

<center>
<font face="arial, helvetica" size"-2">Free JavaScripts provided
by <a href="http://javascriptsource.com">The JavaScript Source</a></font>
</center>
</BODY>

</HTML>

É isto! 8)

Código Javascript para Máscaras

Olá a todos!

O que vou publicar não é exatamente um tutorial, mas uma adaptação a um código Javascript que encontrei para máscaras de campos, e que espero ser útil a quem interessar.

Dias atrás precisei de um código para máscara no padrão ##º##’##”. Como não tinha muito tempo para criar um código de máscara, e por saber da existência de vários códigos do tipo na rede, preferi procurar algum código pronto que resolvesse o problema. O código qu encontrei no site http://javascript.internet.com/forms/dfilter.html funciona para todo tipo de máscara, e dentre todos os que pude testar foi o melhor. Precisei apenas fazer algumas adaptações para compatibilidade com o IE7, e para pegar a digitação de números no teclado numérico auxiliar.

// [dFilter] – A Numerical Input Mask for JavaScript
// Written By Dwayne Forehand – March 27th, 2003
// Please reuse & redistribute while keeping this notice.

var dFilterStep

function dFilterStrip (dFilterTemp, dFilterMask){

    dFilterMask = replace(dFilterMask,’#’,”);
    for (dFilterStep = 0; dFilterStep < dFilterMask.length++; dFilterStep++){         dFilterTemp = replace(dFilterTemp,dFilterMask.substring(dFilterStep,dFilterStep+1),”);     }     return dFilterTemp; } function dFilterMax (dFilterMask){     dFilterTemp = dFilterMask;     for (dFilterStep = 0; dFilterStep 47&&key95&&key<106)) && dFilterNum.length47&&key95&&key<106 ){             var aux = 0;             switch(key){                 case 96:                 aux = 48;                 break;                 case 97:                 aux = 49;                 break;                 case 98:                 aux = 50;                 break;                 case 99:                 aux = 51;                 break;                 case 100:                 aux = 52;                 break;                 case 101:                 aux = 53;                 break;                 case 102:                 aux = 54;                 break;                 case 103:                 aux = 55;                 break;                 case 104:                 aux = 56;                 break;                 case 105:                 aux = 57;                 break;             }             dFilterNum=dFilterNum+String.fromCharCode(aux);         }     }     var dFilterFinal=”;     for (dFilterStep = 0; dFilterStep < dFilterMask.length; dFilterStep++)     {         if (dFilterMask.charAt(dFilterStep)==’#')         {             if (dFilterNum.length!=0)             {                 dFilterFinal = dFilterFinal + dFilterNum.charAt(0);                 dFilterNum = dFilterNum.substring(1,dFilterNum.length);             }             else             {                 dFilterFinal = dFilterFinal + “”;             }         }         else if (dFilterMask.charAt(dFilterStep)!=’#')         {             dFilterFinal = dFilterFinal + dFilterMask.charAt(dFilterStep);         }         //dFilterTemp = replace(dFilterTemp,dFilterMask.substring(dFilterStep,dFilterStep+1),”);     }     textbox.value = dFilterFinal;     return false; } function replace(fullString,text,by) {     // Replaces text with by in string     var strLength = fullString.length, txtLength = text.length;     if ((strLength == 0) || (txtLength == 0))         return fullString;     var i = fullString.indexOf(text);     if ((!i) && (text != fullString.substring(0,txtLength)))         return fullString;     if (i == -1)         return fullString;     var newstr = fullString.substring(0,i) + by;     if (i+txtLength < strLength)         newstr += replace(fullString.substring(i+txtLength,strLength),text,by);     return newstr; } [/sourcecode] Para usar a máscara, basta declarar no evento onKeyDown do componente de entrada (input) o código javascript:return dFilter (event, this, "MASCARA") (onde MASCARA) é o padrão que se deseja. Há um exemplo de utilização no link do código original. Espero que seja útil. Abraços!