Mostrando postagens com marcador Number. Mostrar todas as postagens
Mostrando postagens com marcador Number. Mostrar todas as postagens

quinta-feira, 7 de junho de 2012

Preservando as Permissões do Linux com Script Java Ant Build

O projeto Ant da Apache é uma biblioteca utilíssima que, lendo um pequeno script em XML, é capaz de compilar todo o código do seu projeto, empacotá-lo em arquivo JAR, WAR, EAR, ZIP ou outros formatos e ainda faz mais uma série de outras coisas divertidas, a maioria envolvendo manipulação de arquivos e diretórios.
Embora seja o compilador/empacotador padrão de quase qualquer projeto Java profissional, o Ant lida com manipulação de arquivos e execução de programas tão bem que pode facilmente ler scripts que, com certa criatividade, podem compilar e empacotar programas e C, C++, dentre outras linguagens, além de servir de empacotador para outros projetos genéricos.


  • As Permissões no Linux

(Mais informações sobre as permissões do Linux, vide aqui).
No Linux, as permissões de acesso e execução de arquivos é gravada em forma de atributos de arquivo, e estão dispersas em 3 níveis: usuário (dono do arquivo), os outros membros do grupo principal (do usuário dono do arquivo) e, finalmente, os outros usuários que sobraram. Cada um destes três níveis tem 3 atributos de permissão: leitura somente, leitura e gravação, execução. Assim, temos uma combinação de 7 possibilidades para cada nível, ficando:

0 --- sem permissão
1 --x execução
2 -w- escrita
3 -wx escrita e execução
4 r-- leitura
5 r-x leitura e execução
6 rw- leitura e escrita
7 rwx leitura, escrita e execução

Como se pode perceber, estas permissões, nesta ordem (rwx) formam uma seqüência de 3 bits, que pode então ser representada por um número octal (sim, já que 23 = 8).

0: 000
1: 001
2: 010
3: 011
4: 100
5: 101
6: 110
7: 111

Assim, se temos 3 níveis, e se cada nível pode ser representado por um número octal, então as permissões completas podem ser representadas por uma seqüência de 3 números octais. Exemplos:


rwx rwx rwx = 777
r-x r-x r-x = 555
rwx r-- r-- = 744
e assim por diante...


  • O Problema das Permissões no Linux

Muito bem, e qual o problema disso?? É bem simples, meu caro Watson...
Se a permissão de execução de um arquivo, no Linux, é tão-somente um atributo deste arquivo, como podemos preservá-lo, caso o arquivo seja compilado via Ant?? Suponhamos os seguintes cenários:

1- Que o Ant empacote um programa que tem um arquivo executável (por exemplo, script.sh) gerando um arquivo ZIP. Isso funciona?

Não funciona. O formato ZIP não retém as permissões de arquivos do Linux. Ainda que retivesse, se o programa estiver sendo empacotado numa máquina que roda outro sistema (ex: Windows), as permissões não existirão e, no momento em que o Ant ler o arquivo, elas continuarão perdidas.
Exemplo:

[Este exemplo não faz o que pretendemos]
[arquivos compilados e copiados para a pasta "build"]
<zip destfile="pacote.zip" basedir="build"/>


2- Ah, mas o Ant tem uma task tar. E se ele empacotar o programa como tar??

O formato tar suporta o que queremos, de fato, mas ainda é pouco. Exatamente porque o Java é multiplataforma, a task tar do Ant não usa o programa tar do seu Linux. Usa uma implementação totalmente feita em Java do padrão tar. Só poderia ser assim, para que a task funcionasse inclusive em máquinas que não têm o programa tar, garantindo a compatibilidade entre as diversas plataformas.
O resultado disso é que o arquivo tar é gerado perfeitamente, porém sem as permissões herdadas de cada arquivo, e sim com permissões genéricas.
Exemplo:

[Este exemplo não faz o que pretendemos]
[arquivos compilados e copiados para a pasta "build"]
<tar destfile="pacote.tar">
     <fileset dir="build" includes="**/*"/>
</tar>


  • A Solução do Problema

Finalmente, existem algumas opções que podem resolver o problema. Vamos a elas.

1- Usando o Programa tar de seu Linux

Esta sabemos que não é a melhor opção, embora funcione. Através da task exec, o Ant permite a execução de um programa externo, inclusive passando parâmetros (argumentos) para o mesmo (mais sobre esta task aqui).
O exemplo abaixo indica como chamar o programa tar de um script Ant, passando argumentos que indicam o que deve ser empacotado (diretório ".") e uma lista de arquivos a serem excluídos. O problema é que, como o programa tar normalmente só existe em ambientes Linux, para evitar erros, a task é instruída a só executar se este ambiente for detectado. Esta detecção, internamente, é feita pela propriedade de sistema "os.name".

[Este exemplo não é a melhor opção]
<exec executable="tar" output="/dev/null" os="Linux">
        <arg value="--exclude-from=arquivos_a_excluir.txt"/>
        <arg value="-cvz"/>
        <arg value="--file=${pacote.tar}"/>
        <arg value="."/>
</exec>

A fragilidade desta opção é que, dependendo de um programa externo, ela não funciona em qualquer plataforma. Se você constrói seu sistema sozinho ou se a sua equipe tem o ambiente todo padronizado, então ela pode ser usada. Caso contrário, você teria que criar tantas task exec quanto sistemas usados por sua equipe, e torcer para que, em cada sistema, todos tenham o programa tar no mesmo lugar (ou no PATH).


1- A Forma Correta

A maneira abaixo despreza chamadas para programas externas e constrói a solução ideal. Como isto é possível??
Simples, meu caro Watson: substituímos (ou incluímos) a tag (elemento) fileset pela tag tarfileset. Esta tag é específica da task tar e herda de fileset. Esta faz o mesmo que aquela, ou seja, define uma lista de arquivos para serem considerados, porém com duas propriedades (atributos) a mais, a saber:

- filemode: define as permissões de arquivo Linux que os arquivos incluídos nesta tag deverão ter após incluídos no pacote tar, independente de os arquivos originais terem ou não estas permissões.
- dirmode: faz o mesmo, porém para diretórios.

Assim, é possível se manter ou mesmo trocar as permissões de arquivos no momento de serem empacotados no pacote tar. Evidentemente, devido a esta arquitetura de trabalho, o pacote tar será formado como desejado mesmo que você esteja rodando o script Ant de ambiente Windows, que não suporta as permissões de arquivos Linux. Ou seja: funciona em qualquer plataforma, por mais louca que seja a maneira como ela armazena seus arquivos.

Veja na prática como funciona, pelo exemplo abaixo:

[Este exemplo é o recomendável]
<tar destfile="pacote.tar">
    <fileset dir="build" includes="**/*" excludes="build/script.sh"/>
    <tarfileset dir="build" includes="build/script.sh" filemode="755"/>
</tar>

 No exemplo, selecionamos os arquivo que não são executáveis dentro do elemento fileset, como é feito normalmente. Porém, excluímos o único arquivo diferente, o que queremos que seja um executável. Logo abaixo, incluímos o arquivo executável, porém agora utilizando o elemento (tag) tarfileset, que está instruído a adicioná-lo ao pacote usando as permissões 755.
Como vimos acima, as permissões são representadas por 3 algarismos octais. Os atributos filemode e dirmode de tarfileset somente recebem estes 3 algarismos numéricos. No exemplo, 755 representa: rwx  r-x  r-x, ou seja, todos podem ler e executar, mas somente o dono do arquivo pode modificá-lo.


  • Conclusões

Neste artigo apresentamos como utilizar, no escopo de um script Ant, a task tar, de modo a elegantemente preservar as permissões de arquivos Linux ao se criar um pacote. Claro, estas permissões podem inclusive serem alteradas e/ou criadas no momento do empacotamento, uma vez que esta solução é ágil e flexível. Porque a implementação do tar no Ant é feita em puro Java, a solução dispensa o uso de chamadas a programas externos, o que poderia consistir em fragilidade no script, e, o melhor de tudo, pode ser rodada a partir de qualquer sistema, mesmo aqueles para quem estas permissões não existam nativamente.
Espero ter ajudado e, claro, COMENTEM!!!

domingo, 13 de fevereiro de 2011

Tutorial Java: Convertendo uma String em Decimal, Hexadecimal ou Octal em Inteiro

Este artigo é um complemento para o artigo anterior desta série: Convertendo Binário, Octal, Hexadecimal em Inteiro em Java. A diferença entre os dois é que, no artigo citado, você precisa saber em qual formato (sistema numérico) o número está (precisa saber se está em decimal, em octal, ou em hexadecimal antes de tentar a conversão). Assim, a conversão seria feita facilmente com sobrecargas dos métodos valueOf ou parseInt, ambos da classe Integer. Este artigo ensina uma técnica para converter uma String em formato numérico desconhecido em um Inteiro.
A técnica apresentada no primeiro artigo pode ser muito útil em cenários como:

  • Leitura de dados de um arquivo XML;
  • Entrada de dados oriundo de interconversões;
  • Entradas específicas para determinados formatos (ex: uma classe que exige forçosamente a entrada de uma String em hexadecimal para a perfeita computação de seus dados, e não só a simples conversão desta String em inteiro);

A técnica apresentada neste artigo é mais flexível e pode ser usada em cenários como:

  • Entrada de dados de um componente gráfico (ex: Swing);
  • Entrada de dados de um formulário Web;
  • Entrada de dados de um arquivo texto sem formatação rigorosa (ex: um CSV);


  • Aferindo o Sistema Numérico de uma String e Convertendo-a em Inteiro

Para situações como esta, a classe Integer tem um método que é uma "mão na roda": o método estático decode(String s). Seu retorno é um Integer carregado com o valor correspondente ao que a String represente. Ele reconhece tanto valores decimais quanto octais e hexadecimais, positivos ou negativos.
Note que, para valores hexadecimais, é preciso informar o "0x" ou "0X" na frente, e para valores octais, o "0" na frente. Isto é necessário para que se cumpra a convenção numérica e que se evite ambigüidades no reconhecimento do sistema numérico empregado (sem isso, um valor como "12" estaria em decimal, octal ou hexadecimal?? Você saberia dizer?? Nem eu!!). Se nada for informado, supõe-se que o valor esteja em decimal. O método pode lançar um NumberFormatException. Esta exceção não é marcada (unchecked), mas pode ser sabiamente capturada.

Exemplo: decodificando uma String em hexadecimal:

String hexadecimal = "0xff";
Integer result = Integer.decode(hexadecimal);
System.out.println(result);

// Resultado:
255

Exemplo: decodificando uma String em octal:

String octal = "010";
Integer result = Integer.decode(octal);
System.out.println(result);

// Resultado:
8

Exemplo: decodificando uma String em decimal:

String decimal = "10";
Integer result = Integer.decode(decimal);
System.out.println(result);

// Resultado:
10


  • Interconvertendo os Dados!!

Como o resultado é sempre um Integer, e não um int (primitivo), ele pode ser igualmente auto-vertido para qualquer formato:

String number = "16";
Integer result = Integer.decode(number);
System.out.println(Integer.toHexString(result));
System.out.println(Integer.toOctalString(result));
System.out.println(Integer.toBinaryString(result));

// Resultado:
10
20
10000


  • Observações

1- Lá em cima eu havia afirmado que a técnica apresentada neste artigo permite a conversão de uma String em formato numérico desconhecido, ou seja, você não precisa saber em que formato ela está. É claro que estou fazendo uma metonímia e, assim, refiro-me não a você, desenvolvedor, mas ao código de seu programa. O mais formal seria: seu programa não precisa saber de antemão em que formato está a String para funcionar corretamente. Isto é uma coisa boa pois permite a aplicação do conceito de ocultamento de Orientação a Objetos. Na verdade, a entrada de dados precisa ser de alguma forma conhecida, para evitar ambigüidades ou para evitar o lançamento de uma exceção. Disto, não há escapatória, infelizmente...

2- Todos os exemplos foram testados e nenhum deles se machucou no processo de feitura deste artigo. :-)

3- Feliz?? Triste?? Este artigo salvou a sua vida (ou seu emprego) ou você agora odeia o Pajé?? Não faz mal, pelo menos COMENTE!!!

sábado, 8 de maio de 2010

Convertendo Binário, Octal, Hexadecimal em Inteiro em Java

Essa é outra dica rápida de programação na linguagem Java: como converter uma String representando um número binário, octal ou hexadecimal (e, portanto, que podem conter letras, e não somente números) no inteiro correspondente??

Simples: a classe Integer tem duas sobrecargas do método parseInt: uma recebe somente a String, e supostamente esta String está representando um inteiro em base decimal. A outra recebe a String e a base de cálculo para a conversão. Assim, temos:

Primeira sobrecarga:
int value = Integer.parseInt("16");
System.out.println(value);

Console:
16

Internamente, este método: parseInt(String s) não faz nada além de redirecionar a chamada para a segunda sobrecarga, passando a base 10:

Segunda sobrecarga:
int value = Integer.parseInt("16", 10);
System.out.println(value);

Console:
16

Esta segunda sobrecarga pode receber os valores abaixo como base, usando-os para interpretar a String e fazer a conversão necesária:

2 - Binário
8 - Octal
10 - Decimal
16 - Hexadecimal
27 - base 27 (alguém usa isso??)

Assim, podemos converter binários (usando a base 2), octais (usando a base 8), decimais (usando a base 10) e hexadecimais (usando a base 16), além da não menos esdrúxula base 27.
Exemplos:

Binário:
Integer.parseInt("10", 2); - retorna 2
Integer.parseInt("101", 2); - retorna 5
Integer.parseInt("1100110", 2); - retorna 102

Octal:
Integer.parseInt("10", 8); - retorna 8

Hexadecimal:
Integer.parseInt("f", 16); - retorna 15
Integer.parseInt("a", 16); - retorna 10

Base 27:
Integer.parseInt("Kona", 27); - retorna 411787


  • Outra opção:

Ainda na classe Integer, temos a opção de usar o método valueOf, que tem igualmente duas sobrecargas similares, porém retorna o objeto do tipo Integer de valor correspondente, e não o tipo primitivo. Assim, podemos fazer:

Binário:
Integer i = Integer.valueOf("10", 2);
System.out.println(i.intValue()); - imprime 2

Octal:
Integer i = Integer.valueOf("10", 8);
System.out.println(i.intValue()); - imprime 8

Hexadecimal:
System.out.println(Integer.parseInt("f", 16).intValue());
imprime 15

Uma segunda técnica de conversão numérica e de interconversão entre formatos numéricos pode ser obtida na segunda parte deste artigo, clicando-se aqui!!

Espero que esta dica tenha ajudado!! Qualquer coisa: COMENTEM!!!