MeasureRuler

De Wiki do Leitão
Ir para: navegação, pesquisa

O MeasureRuler é, pela própria tradução, uma régua de medidas. Essa régua de medidas tem a intenção de oferecer aos sistemas a facilidade para converter unidades de medidas da mesma dimensão, ou até mesmo entre dimensões diferentes quando estabelecida a regra de conversão.

Definições:

  • Dimensão - tipo de medida utiliza, como "Comprimento", "Área", "Volume", "Massa (Peso)", etc. Embora estas hoje sejam as dimensões que o BIS suporta, nada impede de se criar dimensões menos usuais como "Energia", "Luminosidade", "Torque", etc..
  • Unidade de Medida - Unidade de medida utilizada para medir a Dimensão desejada. Por exemplo, comprimento pode ser medido em "Metros", "Centimetros", "Pés", "Polegadas", etc. "Massa" pode ser medida em "Arrobas", "Kilos", "Pounds", etc.

A classe MeasureRuler apresenta diversos métodos para conversão de forma. Para mais informações sobre eles consulte o JavaDoc da classe.


MeasureUnit

O MeasureRuler suporta várias dimensões de medidas. Cada dimensão pode conter várias unidades de medidas. A dimensão é definida pela enumeration MeasureDimension. Já as unidades de cada unidade de medida são definidas pela sua própria enumeration que estendem a interface MeasureUnit. Por exemplo:

  • Volume - VolumeUnit
  • Comprimento - LengthUnit
  • Área - AreaUnit
  • Massa (peso) - WeightUnit
  • Unidades - UnitUnit


Interface MeasureUnit

Cada dimensão tem suas unidades de medidas em enumerations próprias. Assim, quando precisamos representar qualquer uma das unidades de medida utilizamos essa interface. Um exemplo: ao cadastrar um produto no sistema, a "unidade básica de estoque" dele pode ser tanto Unidades, quando Metros, Kilos, Metros Quadrados, etc.. Assim, a melhor maneira de definir este atributo no VO é através desta interface.

A interface MeasureUnit apresenta métodos como getDimension(), que retorna a enumeration de MeasureDimension para indicar que Dimensão de Medida esta Unidade pertence.


Persistência da Interface MeasureUnit

MeasureUnitDAOConverter (RFWDAO)

Por tratar-se de uma enumeration, o padrão do FrameWork é que no banco seja um campo do tipo varchar(50) (O limite recomendado de enums no RFW é de 50 chars). Mas, uma vez que a MeasureUnit é uma interface, o RFWDAO não sabe como proceder para persisti-la e reconstruir o VO com os dados do banco. Para isso temos de utilizar uma RFWDAOConverter. E para simplificar, o MeasureRuler já oferece uma implementação pronta para isso a MeasureUnitDAOConverter.


Note 64.png
Classe Disponível no RFW.DAO
Apesar do Measure ser disponibilizado no RFW.Kernel, as classes de persistência e conversão estão disponibilizadas apenas no módulo RFW.DAO, afinal essas classes só fazem sentido em conjunto com o RFW.


Para definir um atributo no VO que seja do tipo MeasureUnit devemos ter algo como:

Java 256.png Exemplos de Declaração
@BISDAOConverter(converterClass = MeasureUnitDAOConverter.class)
@BISMetaGenericField(caption = "Unidade de Medida", required = true)
private MeasureUnit measureUnit = null;


Sempre que o RFWDAO ler a String no banco de dados que representa a unidade de medida, o Converter conseguirá descobrir de qual enumeration devemos realizar o "valueOf(...)".


Persistindo sem o RFWDAO

Ao persistir o RFWVO sem o RFWDAO, será necessário identificar como o seu sistema salva as enumerações. Embora existam diversas maneiras de se resolver, se seguir o método do RFWDAO, em que as enumerations são salvas somente pelos seus nomes, ao recuperar o nome do banco de dados e utilizar o ".valueOf()" a operação falhará, já que não sabemos de qual enumeration a unidade pertence.

Para resolver isso o MeasureRuler tem o seguinte método:

MeasureRuler.valueOf(...);

Este método identifica a unidade pelo nome, entre todas as dimensões existentes no MeasureRuler.


Stop 256.png
Unidades de Medidas Iguais
Por conta de termos várias unidades de medidas de dimensões diferentes representadas em enumerations diferentes, o Java permitirá que tenhamos uma unidade de medida "X" tanto em VolumeUnit quanto em AreaUnit. Embora o Java não tenha problemas para compilar esse código, ao ser persistido no banco de dados (salvo apenas o NAME da enumeration), a recuperação do objeto é baseada apenas no nome da enumeration. Isso fará com que seja sempre retornada pela primeira enumeration que o código procura.

Para evitar esse problemas as unidades de medidas devem sempre ter nomes diferentes mesmo que em dimensões distintas. Embora não conheça nenhuma unidade de medida que tenha o mesmo o nome e represente coisas diferentes, é bom que o DEV tenha ciência dessa situação.

Utilização da Régua

Para realizar a conversão das unidades de medidas devemos focar nos métodos .convertTo(...) da classe MeasureRuler. Há métodos simples utilizados para conversão dentro da mesma dimensão (sem utilização de equivalências) até métodos completos com a entrada de equivalências e definição da precisão de casas decimais.

Exemplos de Utilização

Veja alguns exemplos de utilização da MeasureRuler:

Java 256.png Conversão de Unidade de Medida dentro da Mesma Dimensão
  // Converte 1000ml em Litros, retornará um BigDecimal com o valor de 1.000
  MeasureRuler.convertTo(new BigDecimal("1000"), VolumeUnit.MILLILITER, VolumeUnit.LITER);

  // Converte 1000ml em Litros, mas dessa vez retornará com a precisão de 6 casas decimais.
  MeasureRuler.convertTo(new BigDecimal("1000"), VolumeUnit.MILLILITER, VolumeUnit.LITER, 6);

  //  Converte 1000g em Kilogramas, com uma precisão de 6 casas decimais, retornará o valor 1.000000
  MeasureRuler.convertTo(new BigDecimal("1000"), WeightUnit.GRAM, WeightUnit.KILOGRAM, 6);


Para converter entre diferentes dimensões (grandezas de medidas diferentes), vamos primeiro estabelecer uma regra de equivalência entre as grandezas, podendo ser feita com qualquer unidade de medida da grandeza.

Vejamos um exemplo, imagine um sistema de marcenaria que precise cadastrar uma chapa de madeira utilizada para móveis. No estoque e durante o processo de compra, referenciamos o item por unidades, pois fica mais fácil de fazer o pedido e comparar preços. Mas no momento de calcular o frete, precisamos saber qual o peso de cada chapa, ou qual o peso final do móvel produzido, de acordo com a "quantidade de chapa" (medida em metros quadrados (área)) que foi recortado e utilizado. Para ter todas essas informações precisamos estabelecer a relação entre as grandezas Unidades, Massa (Peso) e Área.

Imagine que após verificar o produto, descobrimos que a chama tem medida de 2m x 1,8m (=3,6m²), e que ela pesa 76Kg. Assim, montamos nossos dados de equivalência:


Java 256.png Criando Regras de Equivalência
  // Regras de Equivalência
  MeasureRulerEquivalence eqVO = new MeasureRulerEquivalence();
  eqVO.getMeasureUnitHash().put(UnitUnit.UNIT, new BigDecimal("1")); // Definimos que cada 1 Unidade...
  eqVO.getMeasureUnitHash().put(WeightUnit.KILOGRAM, new BigDecimal("76")); // equivale a 76Kg...
  eqVO.getMeasureUnitHash().put(AreaUnit.SQUAREMETER, new BigDecimal("3.6")); // e a 3,6m²


Com as regras de equivalência o 'MeasureRuler conseguirá converter unidades conforme a necessidade. Vamos imaginar que o marceneiro precisa saber quantas chapas vamos precisar para cobrir uma área de 40m². Assim convertemos na régua:


Java 256.png Convertendo Entre as Dimensões
  // Chamamos a MeasureRuler, mas agora passando as regras de conversão
  BigDecimal v = MeasureRuler.convertTo(eqVO, new BigDecimal("40"), AreaUnit.SQUAREMETER, UnitUnit.UNIT, 6);

  // o Valor de v será equivalente a 11.111111, com 6 casas decimais de precisão, conforme solicitado


Unidades de Medidas Personalizadas

O MeasureRuler permite que o usuário crie suas próprias unidades de medidas bastando apenas definir um nome e um símbolo em uma classe que estenda a interface MeasureUnit. Depois, para que a unidade de medida se torne funcional basta adiciona-la a régua de equivalências como no exemplo anterior.

Imagine que o marceneiro do exemplo anterior, é de uma cidade esquisita e que lá existe uma unidade de medida "XYZ", utilizada localmente, cujo símbolo é "Xz" e que representa 7 Unidade. Para adicionar essa nova unidade na nossa equivalência bastaria criar um objeto implementando a interface MeasureUnit, como dito anteriormente. Veja o exemplo:


Java 256.png Unidade de Medida Personalizada
  // Mesma régua de equivalências do exemplo anterior
  MeasureRulerEquivalence eqVO = new MeasureRulerEquivalence();
  eqVO.getMeasureUnitHash().put(UnitUnit.UNIT, new BigDecimal("1"));
  eqVO.getMeasureUnitHash().put(WeightUnit.KILOGRAM, new BigDecimal("76"));
  eqVO.getMeasureUnitHash().put(AreaUnit.SQUAREMETER, new BigDecimal("3.6"));

  // Vamos utilizar a classe CustomMeasureUnitGeneric que implementa a interface necessária, para criar nossa unidade de medida personalizada
  CustomMeasureUnitGeneric xyz = new CustomMeasureUnitGeneric("XYZ", "Xz");

  // Agora incluímos nossa unidade de medida com o peso de 7 (pois é 7Xz para 1 Unidade)
  eqVO.getMeasureUnitHash().put(xyz, new BigDecimal("7"));


Note que o peso foi 7 porque nossa régua está criada com peso de 1 em unidades. Se nossa equivalência fosse 7Xz para 2 Unidades, teríamos que trocar o peso de 7 para 3,5Xz. Quando montamos a régua de equivalência temos que colocar os pesos que sejam compatíveis. No nosso exemplo, o código seria lido como se fosse a seguinte igualdade:

1 Unidade = 76Kg = 3,6m² = 7Xz


Equivalências Despareadas

Este é um recurso criado apenas para simplificar e permitir que o MeasureRuler faça os cálculos pra você no momento de adicionar as equivalências desconectadas.

Por exemplo, imagine que você tenha as seguinte igualdades de equivalência:

1 Unidade = 36g
2m² = 12,4Kg

Nessa situação teríamos que calcular as igualdade de maneira que se tornassem todas proporcionais, unificando a unidade de medida de Massa em "g" ou em "Kg" e recalculando a razão da outra unidade de medida. Até termos algo como:

1 Unidade = 36g => 0,001 Unidade = 0,036Kg => 0,3444444 Unidades = 12,4Kg
2m² = 12,4Kg

para que então possamos estabelecer a equivalência entre todos:

0,3444444 Unidades = 12,4Kg = 2m²


Para evitar esse trabalho todo externo a interface MeasureRulerEquivalenceInterface tem um método "default" chamado addComparativeEquivalence que permite adicionar essas referências descasadas diretamente. Vejamos o código no nosso exemplo acima:


Java 256.png Unidade de Medida Descasada
  // Criamos a régua e incluímos nossa primeira equivalência ligando duas dimensões
  MeasureRulerEquivalence eqVO = new MeasureRulerEquivalence();
  eqVO.getMeasureUnitHash().put(UnitUnit.UNIT, new BigDecimal("1"));
  eqVO.getMeasureUnitHash().put(WeightUnit.GRAM, new BigDecimal("36"));

  // Agora já temos Unidades e Massa na régua de equivalência, podemos adicionar outra dimensão utilizando uma dessas referência
  eqVO.addComparativeEquivalence(new BigDecimal("2"), AreaUnit.SQUAREMETER, new BigDecimal("12.4"), WeightUnit.KILOGRAM);


Internamente o método fará a conversão e estabelecerá os pesos corretos para conversão.


Persistindo as Unidades de Medidas Personalizadas

Cada sistema tem autonomia para tratar a persistência como preferir, mas a metodologia utilizada pelo RFW, e que é praticada pelo MeasureUnitDAOCOnverter quando utilizado o RFWDAO é a seguinte:

Primeiro vamos relembrar que a MeasureUnit padrão é uma enumeration, e que o RFW mantém as enumeration com o limite máximo de 50 caracteres. Inclusive sugerindo que o campo no banco seja um varchar(50).

Segundo, a unidade de medida personalizada é composta apenas de um nome e um símbolo.

Com essas diretrizes, a solução implementada foi, sempre que a MeasureUnit recebida para persistência for uma CustomMeasureUnit, será montada uma String no formato:

"#" + value.getSymbol() + "|" + value.name()

Utilizamos o primeiro carácter "#" para identificar que se trata de uma serialização de CustomMeasureUnit, e um "|" para separar o Símbolo do nome. Ao reler o conteúdo no banco de dados, detectamos o primeiro carácter, se encontramos o "#" montamos uma CustomMeasureUnit com o objeto CustomMeasureUnitGeneric fazendo o parser da serialização anterior.


Note 64.png
Objeto da Desserialização
Note que se a aplicação utilizou seu próprio objeto para implementar a interface CustomMeasureUnit o MeasureUnitDAOConverter não é capaz de retornar um objeto personalizado.

Como alternativa você pode implementar seu próprio converter e retornar seu objeto necessário. Ou, ainda, converter o objeto retornado quando e se for necessário. Se apenas forem necessários os métodos da interface, ele pode ser mantido.


Note 64.png
Limite dos Tamanhos
Seguindo essa metodologia de serializar o objeto personalizado no campo de 50 caracteres destinado a enumeration, temos que lembrar que o limite de Nome da Unidade de Medida + Simbolo será de 48, pois temos mais 2 caracteres de sinalização.

Esse tamanho precisa ser validado em alguma parte do sistema caso sejam estabelecidos esses parâmetros. O MeasureRuler não inclui essa validação no seu código pois ela pode ser diferente para cada sistema.

Validação da Régua de Equivalência

É importante tomar cuidado com as informações fornecidas dentro da régua de equivalências para que não sejam incoerentes.


Stop 256.png
Informações Redundantes
Só devemos ter uma unidade de medida de cada dimensão. Incluir na Hash múltiplas informações da mesma dimensão é desnecessário, e podem resultar em resultados "aleatórios" se elas não forem coerentes.

Exemplos:

Incluir 1Kg, 1000g e 50Unidades - A hash aceitará a unidade de medida KILOGRAM e GRAM, mas a informação é redundante, mas não causará nenhum problema.

Incluir 1Kg, 500g e 50Unidades - Informar que 1Kg é equivalente à 500g é uma informação incoerente. Ao procurar as equivalências para converter Unidades em Massa, o MeasureRuler pode hora utilizar a informação de 1Kg - 50 Unidades, hora utilizar 500g - 50Unidades. Dependendo da informação que encontrar primeiro na Hash o resultado será diferente.



A classe MeasureRuller contém um método de validação validateMeasureRulerEquivalence() para garantir que as informações da Hash de equivalências não estão redundantes ou incoerentes.