/*
* Units - Temporary implementation for Geotools 2
* Copyright (C) 1998 University Corporation for Atmospheric Research (Unidata)
* 1998 Bill Hibbard & al. (VisAD)
* 1999 Pêches et Océans Canada
* 2000 Institut de Recherche pour le Développement
* 2002 Centre for Computational Geography
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details (http://www.gnu.org/).
*
*
* This package is inspired from the units package of VisAD.
* Unidata and Visad's work is fully acknowledged here.
*
* THIS IS A TEMPORARY CLASS
*
* This is a placeholder for future Unit
class.
* This skeleton will be removed when the real classes from
* JSR-108: Units specification will be publicly available.
*/
package org.geotools.units;
// Formattage de textes
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.geotools.resources.CharUtilities;
import org.geotools.resources.Utilities;
import org.geotools.resources.XMath;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.WeakHashSet;
/**
* Classe chargée du formattage ou de l'interprétation des symboles d'une unité.
* Cette classe n'est qu'un premier jet. Elle ne contient pas encore d'API qui
* permettrait de contrôler la façon d'écrire les unités. Plus important, le contrat général voulant
* que tout objet produit par {@link #format(Object)} soit lissible par {@link #parse(String)} n'est
* pas garanti dans la version actuelle.
*
* @source $URL$
* @version 1.0
* @author Martin Desruisseaux
*
* @deprecated Replaced by the {@link javax.units.Unit} framework.
*/
final class UnitFormat {
/**
* Format par défaut à utiliser pour construire et interpréter les symboles des unités.
* Ce format sera utilisé par les constructeurs des différentes classes {@link Unit}
* pour créer des symboles, ainsi que par la méthode {@link Unit#getUnit} pour interpréter
* un symbole.
*/
static final UnitFormat DEFAULT=new UnitFormat();
/**
* Banque des objets qui ont été précédemment créés et
* enregistrés par un appel à la méthode {@link #intern}.
*/
private static final WeakHashSet pool=Prefix.pool;
/**
* Inverse d'une petite valeur servant à éviter des erreurs d'arrondissements.
* Cette valeur est définie arbitrairement à 2^24, soit environ 1.678E+7.
*/
private static final double INV_EPS = 16777216;
/**
* Ordre de préférences des classes d'unités. Cette ordre sera pris en
* compte si plusieurs unités ont été trouvés pour un symbole donné.
*/
private static final Class[] PRIORITIES=new Class[] {
BaseUnit.class,
DerivedUnit.class,
ScaledUnit.class,
OffsetUnit.class
};
/**
* Symbole représentant la multiplication d'une unité par un facteur.
*/
private static final char SCALE_SYMBOL = '\u00D7';
/**
* Symbole représentant la multiplication de deux unités.
*/
private static final char DOT_SYMBOL = '\u22C5'; // TODO: on devrait plut�t utiliser '\u22C5', mais ce symbole n'est pas affich� correctement.
/**
* Symbole représentant la division de deux unités.
*/
private static final char SLASH_SYMBOL = '/';
/**
* Symbole de l'opérateur exposant.
*/
private static final char EXPONENT_SYMBOL='^';
/**
* Symbole représentant l'ouverture d'une parenthèse.
*/
private static final char OPEN_SYMBOL = '(';
/**
* Symbole représentant la fermeture d'une parenthèse.
*/
private static final char CLOSE_SYMBOL = ')';
/**
* Construit un objet qui lira et écrira
* des unités avec les paramètres par défaut.
*/
public UnitFormat()
{}
/**
* Construit un symbole à partir du facteur spécifié. Ce sera le symbole
* de l'unité de base avec son exposant. Par exemple "m", "m²" ou "kg^-1".
*/
final StringBuffer format(final Factor factor, final StringBuffer buffer) {
if (factor.power!=0) {
buffer.append(factor.baseUnit.symbol);
if (factor.power!=1) {
final String power = String.valueOf(factor.power);
final int length = power.length();
final int initPos = buffer.length();
for (int i=0; i0) {
if (numeratorCount!=0) buffer.append(DOT_SYMBOL);
buffer=format(factor, buffer);
numeratorCount++;
} else if (factor.power<0) {
denominatorCount++;
}
}
/*
* Ajoute au buffer tous les termes qui se trouvent comme dénominateurs
* (puissance négative), s'il y en a.
*/
if (denominatorCount!=0) {
if (numeratorCount==0) buffer.append('1');
buffer.append(SLASH_SYMBOL);
if (denominatorCount!=1) buffer.append(OPEN_SYMBOL);
denominatorCount=0;
for (int i=0; i"\u00D70,9144\u00A0m"
* pour représenter un yard. Si le symbole unit
autorise l'utilisation de préfix, un
* préfix pourra être placé devant le symbole plutôt que d'écrire le facteur. C'est le cas par
* exemple des centimètres qui peuvent être écrits comme "cm".
*/
final StringBuffer formatScaled(double amount, final SimpleUnit unit, StringBuffer buffer) {
String symbol = unit.symbol;
final int length = symbol.length();
final int initPos = buffer.length();
final PrefixSet prefix = unit.prefix;
if (prefix!=null) {
/*
* Commence par vérifier si le symbole de unit
commençait
* déjà par un des préfix. Si oui, on supprimera cet ancien préfix.
*/
final String unprefixedSymbol = unit.getUnprefixedSymbol();
if (symbol.endsWith(unprefixedSymbol)) // Test only to make sure...
{
final Prefix p=prefix.getPrefix(symbol.substring(0, symbol.length()-unprefixedSymbol.length()));
if (p!=null) {
symbol=unprefixedSymbol;
amount *= p.amount;
}
}
/*
* Essaie de placer un nouveau préfix devant
* le symbole, en fonction de l'échelle.
*/
final Prefix p=prefix.getPrefix(amount);
if (p!=null) {
symbol = p.symbol+symbol;
amount /= p.amount;
}
}
/*
* Si amount
est presqu'une puissance de 10, arrondi
* à la puissance de 10 la plus proche. Cette étape vise à réduire
* certaines erreurs d'arrondissement.
*/
final double power = Math.rint(XMath.log10(amount)*INV_EPS)/INV_EPS;
if (power==Math.rint(power)) amount=XMath.pow10(power);
/*
* Si on n'a pas pu placer un préfix devant les unités,
* alors on écrira un symbole de multiplication.
*/
if (amount!=1) {
final NumberFormat format=NumberFormat.getNumberInstance();
buffer.append(SCALE_SYMBOL);
format.format(amount, buffer, new FieldPosition(0));
if (length!=0) {
buffer.append('\u00A0'); // No-break space
}
}
buffer.append(symbol);
return buffer;
}
/**
* Renvoie une chaîne de caractères représentant le décalage d'une unité. Par exemple cette représentation
* pourrait être de la forme "+273.15\u00A0K
pour représenter des degrés Celsius.
*/
final StringBuffer formatOffset(final double offset, final Unit unit, StringBuffer buffer) {
final String symbol = unit.toString();
final int length = symbol.length();
final NumberFormat format = NumberFormat.getNumberInstance();
if (format instanceof DecimalFormat) {
final DecimalFormat cast=(DecimalFormat) format;
if (cast.getPositivePrefix().trim().length()==0) {
cast.setPositivePrefix("+");
}
}
format.format(offset, buffer, new FieldPosition(0));
if (length!=0) {
buffer.append('\u00A0'); // No-break space
buffer.append(symbol);
}
return buffer;
}
/**
* Retourne les unités qui correspondent au symbole spécifié. Si plus d'une
* unité correspond au symbole spécifié, une unité arbitraire sera choisie.
*
* @param symbol Symbole des unités recherchées. Cet argument ne doit pas être nul.
* @return Si les unités ont été trouvés, l'objet {@link Unit} qui les représentent.
* Sinon, un objet {@link String} contenant la portion de chaîne qui n'a pas
* été reconnue.
* @throws IllegalArgumentException si les parenthèses ne sont pas équilibrées.
*/
final Object parse(final String symbol) throws IllegalArgumentException {
final Set set=new HashSet(11);
final String unrecognized=parse(symbol.replace('*', DOT_SYMBOL), set);
final Unit[] units = (Unit[]) set.toArray(new Unit[set.size()]);
switch (units.length) {
case 0: return unrecognized;
case 1: return units[0];
default: return selectUnit(units);
}
}
/**
* Recherche des unités qui correspondent au symbole spécifié. Les unités trouvés seront ajoutés
* dans l'ensemble set
. Si aucune unité n'a été trouvée, la taille de set
* n'aura pas augmentée. Dans ce cas, cette méthode retourne les caractères qui n'ont pas été reconnus.
*
* @param symbol Symbole des unités recherchées.
* @param set Ensemble dans lequel placer les unités trouvées.
* @return null
si des unités ont été trouvées, ou sinon la portion
* de la chaîne symbol
qui n'a pas été reconnue.
* @throws IllegalArgumentException si les parenthèses ne sont pas équilibrées.
*/
private String parse(String symbol, final Set set) throws IllegalArgumentException {
symbol=symbol.trim();
final int initialSize=set.size();
/*
* Ignore les parenthèses qui se trouvent au début ou à la fin des unités. Les éventuelles
* parenthèses qui se trouverait au milieu ne sont pas pris en compte maintenant. Elles le
* seront plus tard. On ignore toujours le même nombre de parenthèses ouvrantes au début
* que de parenthèses fermantes à la fin.
*/
if (true) {
int lower=0;
int upper=0;
int level=0;
int index=0;
final int length=symbol.length();
while (index=0 ? CLOSE_SYMBOL : OPEN_SYMBOL)));
}
if (index==length) {
upper=length-upper;
if (lower>upper) lower=upper;
if (upper>lower) upper=lower;
upper=length-upper;
symbol=symbol.substring(lower, upper);
}
}
/*
* Recherche les symboles de divisions ou de multiplications. Si un tel symbole est trouvé, on lira séparament
* les unités qui précèdent et qui suivent ce symbole. On ne prend en compte que les symboles qui ne se trouvent
* pas dans une parenthèse. Si un symbole se trouve dans une parenthèse, il sera pris en compte plus tard.
*/
if (true) {
int level=0;
String unrecognized=null;
for (int i=symbol.length(); --i>=0 && level<=0;) {
final int power;
switch (symbol.charAt(i)) {
case OPEN_SYMBOL: level++; continue;
case CLOSE_SYMBOL: level--; continue;
case DOT_SYMBOL: power=+1; break;
case SLASH_SYMBOL: power=-1; break;
default : continue;
}
if (level!=0) continue;
/*
* Un signe de multiplication ou d'addition a été trouvé.
* Lit d'abord les unités avant ce signe, puis après ce signe.
*/
String tmp;
final Set unitsA=new HashSet(11);
final Set unitsB=new HashSet(11);
tmp=parse(symbol.substring(0,i), unitsA); if (unrecognized==null) unrecognized=tmp;
tmp=parse(symbol.substring(i+1), unitsB); if (unrecognized==null) unrecognized=tmp;
for (final Iterator itA=unitsA.iterator(); itA.hasNext();) {
final Unit unitA = (Unit) itA.next();
for (final Iterator itB=unitsB.iterator(); itB.hasNext();) {
final Unit unitB = (Unit) itB.next();
try {
final Unit unit;
switch (power) {
case -1: unit=unitA.divide (unitB); break;
case +1: unit=unitA.multiply(unitB); break;
default: unit=unitA.multiply(unitB.pow(power)); break;
}
set.add(unit);
} catch (UnitException exception) {
// ignore incompatible units.
}
}
}
return (set.size()==initialSize) ? unrecognized : null;
}
if (level!=0) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NON_EQUILIBRATED_PARENTHESIS_$2,
symbol, String.valueOf(level>=0 ? CLOSE_SYMBOL : OPEN_SYMBOL)));
}
}
/*
* Parvenu à ce stade, on n'a détecté aucun symbole de multiplication ou de division
* et aucune parenthèses. Il ne devrait rester que le symbole de l'unité, éventuellement
* avec son préfix et un exposant. On tente maintenant d'interpréter ce symbole.
*/
int power = 1;
boolean powerParsed = false;
/*
* La boucle suivante sera exécutée deux fois. La première fois, on n'aura pas tenté de prendre en compte
* une éventuelle puissance après le symbole (par exemple le '2' dans "m²"), parce que le symbole avec sa
* puissance a peut-être été déjà explicitement définie. Si cette tentative a échoué, alors le deuxième
* passage de la boucle prendra en compte un éventuel exposant.
*/
while (true) {
final int length=symbol.length();
for (int lower=0; lowersymbol.
* S'il a fallu sauter des caractères pour trouver cette unité, alors les caractères ignorés doivent
* être un préfix. On tentera d'identifier le préfix en interrogeant la liste des préfix autorisés
* pour cette unité.
*/
if (lower!=0) {
if (unit.prefix==null) continue;
final Prefix prefix=unit.prefix.getPrefix(symbol.substring(0, lower));
if (prefix==null) continue;
unit=unit.scale(prefix.amount);
}
/*
* Tente maintenant d'élever les unités à une puissance,
* s'ils sont suivit d'une puissance.
*/
try {
set.add(unit.pow(power));
} catch (UnitException exception) {
continue;
}
}
}
/*
* Si c'est le second passage de la boucle, la puissance a déjà été
* prise en compte. On terminera alors cette méthode maintenant.
*/
if (powerParsed) {
return (set.size()==initialSize) ? symbol : null;
}
powerParsed=true;
/*
* Si aucune unité n'a été trouvée lors du premier passage de la boucle, tente maintenant de prendre en compte
* une éventuelle puissance qui aurait été spécifiée après les unités (comme par exemple le '2' dans "m²"). On
* supposera que la puissance commence soit après le dernier caractère qui n'est pas un exposant, ou soit après
* le symbole '^'.
*/
int expStart;
int symbolEnd=symbol.lastIndexOf(EXPONENT_SYMBOL);
if (symbolEnd>=0) {
// Positionne 'expStart' après le symbole '^'.
expStart = symbolEnd+1;
} else {
for (symbolEnd=length; --symbolEnd>=0;) {
if (!CharUtilities.isSuperScript(symbol.charAt(symbolEnd))) {
symbolEnd++;
break;
}
}
// Il n'y a pas de symbole '^' à sauter pour 'expStart'.
expStart = symbolEnd;
}
/*
* Maintenant qu'on a séparé le symbole de l'exposant, tente d'interpréter l'exposant. Si l'interprétation
* échoue, ou s'il n'y a pas d'exposant ou de symbole, alors ce n'est pas la peine de faire le deuxième
* passage de la boucle; on fera donc un "break".
*/
if (symbolEnd>=1 && expStart=0;) {
tmp.setCharAt(i, CharUtilities.toNormalScript(tmp.charAt(i)));
}
powerText = tmp.toString();
}
symbol=symbol.substring(0, symbolEnd);
try {
power=Integer.parseInt(powerText);
} catch (NumberFormatException exception) {
// TODO: le message d'erreur de 'Unit.getUnit(String)' n'est pas
// vraiment approprié lorsqu'on retourne 'powerText'.
return (set.size()==initialSize) ? powerText : null;
}
} else {
return (set.size()==initialSize) ? symbol : null;
}
}
}
/**
* Sélectionne une unité. Cette méthode est appelée automatiquement par la méthode
* {@link #parse} si elle a trouvé plusieurs unités qui utilisent le même symbole.
* L'implémentation par défaut tentera de retourner de préférence une unité de la
* classe {@link BaseUnit} ou {@link DerivedUnit}.
* Les classes dérivées peuvent redéfinir cette méthode pour sélectionner une unité
* selon d'autres critères, par exemple en demandant à l'utilisateur de choisir.
*
* @param units Liste d'unités parmi lesquelles il faut faire un choix.
* La longueur de ce tableau sera d'au moins 2.
* @return Unité choisie. Il n'est pas obligatoire que cette unité fasse
* partie du tableau units
original.
*/
protected Unit selectUnit(final Unit[] units) {
for (int i=0; i