/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.constellation.numeric.table;
// J2SE dependencies
import java.lang.reflect.Field;
import java.nio.DoubleBuffer;
import java.util.Arrays;
import static java.lang.Double.NaN;
import static java.lang.Double.isNaN;
import static java.lang.System.arraycopy;
import static org.apache.sis.util.ArraysExt.isSorted;
import static org.constellation.numeric.table.DataOrder.*;
// Static imports
/**
* Un vecteur ordonné dont les données sont spécifiées dans un buffer.
*
* @version $Id$
* @author Martin Desruisseaux
*/
final class BufferedOrderedVector extends OrderedVector {
/**
* Les valeurs de ce vecteur. Il est de la responsabilité de l'utilisateur de s'assurer que
* ces données restent en ordre croissant ou décroissant. Pour des raisons de performance,
* ça ne sera pas vérifié.
*/
private final DoubleBuffer data;
/**
* Construit un vecteur ordonné pour les données spécifiées. Les données doivent obligatoirement
* être en ordre croissant ou décroissant. Pour des raisons de performances (et aussi parce qu'il
* n'est pas possible de s'assurer que l'utilisateur ne modifiera pas les données après la
* construction de ce vecteur), ça ne sera pas vérifié.
* <p>
* Les premières données prises en compte seront les données à la {@linkplain DoubleBuffer#position()
* position courante}. Le nombre de données prises en compte sera le {@linkplain DoubleBuffer#remaining()
* nombre de données restantes}. Après la construction du vecteur, les changements de
* {@linkplain DoubleBuffer#position() position} ou de {@linkplain DoubleBuffer#limit() limite}
* de l'objet {@code data} donné en argument n'affecteront pas ce vecteur. Toutefois tout changement
* des données contenues dans le buffer affecteront ce vecteur.
*/
public BufferedOrderedVector(final DoubleBuffer data) {
this.data = data.slice();
}
/**
* Retourne l'ordre des données dans ce vecteur. Cette méthode évalue l'ordre chaque fois qu'elle
* est invoquée. Elle peut donc servir à déterminer si un changement de données a eu un impact sur
* leur ordre.
*/
public DataOrder getDataOrder() {
double previous = NaN;
boolean strict = true;
boolean ascending = true;
boolean determined = false;
data.rewind();
while (data.hasRemaining()) {
final double value = data.get();
if (isNaN(value)) {
continue;
}
if (!isNaN(previous)) {
if (value > previous) {
if (!determined) {
determined = true;
ascending = true;
} else if (!ascending) {
return UNORDERED;
}
} else if (value < previous) {
if (!determined) {
determined = true;
ascending = false;
} else if (ascending) {
return UNORDERED;
}
} else {
strict = false;
}
}
previous = value;
}
if (!determined) {
return FLAT;
}
return (ascending) ?
(strict ? STRICTLY_ASCENDING : ASCENDING) :
(strict ? STRICTLY_DESCENDING : DESCENDING);
}
/**
* Retourne la longueur de ce vecteur.
*/
public final int length() {
return data.limit();
}
/**
* Retourne la valeur à l'index spécifié. La valeur de cet index peut varier de 0 inclusivement
* jusqu'à {@link #length} exclusivement.
*
* @param index La valeur de l'index.
* @return La valeur du vecteur à l'index spécifié.
* @throws IndexOutOfBoundsException si l'index est en dehors des limites permises.
*/
public final double get(final int index) throws IndexOutOfBoundsException {
return data.get(index);
}
/**
* {@inheritDoc}
*/
public boolean locate(final double x) {
/*
* Positionne la plage [upper..lower] aux extrémités du vecteur. S'il y a des NaN, ils seront
* pris en compte en peu plus bas. 'xlo' et 'xhi' seront les valeurs de x correspondantes.
*/
value = x;
lower = 0;
upper = data.limit();
if (--upper < 0) {
return false;
}
double xhi = data.get(upper);
double xlo = data.get(lower);
boolean ascending = (xlo < xhi);
/*
* Si la valeur de x spécifiée n'est pas comprise dans la plage de valeurs de ce vecteur,
* ignore les NaN et vérifie si la valeur est égale à celle de l'une des extrémités. Note:
* le '!' dans l'expression ci-dessous est nécessaire pour attraper les valeurs NaN.
*/
while (ascending ? !(xlo<x && x<xhi) : !(xlo>x && x>xhi)) {
if (x == xlo) {upper=lower; return true;}
if (x == xhi) {lower=upper; return true;}
boolean changed = false;
if (isNaN(xlo)) {
do if (++lower > upper) return false;
while (isNaN(xlo = data.get(lower)));
changed = true;
}
if (isNaN(xhi)) {
do if (--upper < lower) return false;
while (isNaN(xhi = data.get(upper)));
changed = true;
}
if (!changed) {
return false;
}
ascending = (xlo < xhi);
}
/*
* A partir de ce point, on a l'assurance qu'il existe au moins une donnée autre que NaN
* et que la valeur de 'x' est comprise entre 'xlo' et 'xhi'. On peut lancer la recherche
* bilinéaire.
*/
search: while (upper - lower > 1) {
int k = (upper+lower) >> 1; // Indice au centre de la plage [lower..upper]
int scan = 0; // Utilisé en cas de valeurs NaN seulement.
do {
assert k>lower && k<upper : k;
final double xk = data.get(k);
if (x < xk) {
if (ascending) upper = k;
else lower = k;
continue search;
}
if (x > xk) {
if (ascending) lower = k;
else upper = k;
continue search;
}
if (x == xk) {
lower = upper = k;
return true;
}
/*
* Le code suivant ne sera exécuté que si l'on vient de tomber sur un NaN.
* On recherche linéairement une valeur qui ne soit pas NaN autour de k en
* testant dans l'ordre k-1, k+1, k-2, k+2, k-3, k+3, etc.
*/
assert isNaN(xk) || isNaN(x) : xk;
// Dans la ligne ci-dessous, le premier (k-scan) restore la valeur originale de k
// tandis que le second (k-scan) anticipe sur le changement de 'scan' à venir.
k -= (scan << 1);
if (scan >= 0) {
scan = ~scan; // Equivaut à (-scan - 1)
if (--k > lower) {
assert k-scan == (upper+lower) >> 1 : scan; // (k-scan) devrait être la valeur originale.
continue;
}
k -= (scan << 1); // Annule le test de 'k-scan' et passe au test de 'k+scan'.
}
scan = -scan;
assert scan > 0 : scan;
assert k-scan == (upper+lower) >> 1 : scan; // (k-scan) devrait être la valeur originale.
} while (k < upper);
/*
* On atteint ce point si aucune valeur autre que NaN n'a été trouvée entre
* 'lower' et 'upper'. Les index 'lower' et 'upper' toutefois sont valides.
*/
break;
}
assert data.get(lower) <= x : lower;
assert data.get(upper) >= x : upper;
return true;
}
/**
* {@inheritDoc}
*/
public boolean locateAroundIndex(final int index) {
lower = upper = index;
final int length = data.limit();
do if (++upper >= length) {
return false;
} while (isNaN(data.get(upper)));
do if (--lower < 0) {
return false;
} while (isNaN(data.get(lower)));
value = data.get(index);
return true;
}
/**
* {@inheritDoc}
*/
public boolean copyIndexInto(final int[] index) {
int lower = this.lower; // Protect from changes.
int upper = this.upper; // Protect from changes.
final int length = data.limit();
int center = index.length;
if (center >= 2) {
center >>= 1;
int i = center;
/*
* Si 'upper' et 'lower' sont identiques, on n'écrira pas 'lower' afin de ne pas
* répéter deux fois le même index. On écrira seulement 'upper.' La boucle 'loop'
* copie au début du tableau 'index' les index qui précèdent 'lower'.
*/
if (upper != lower) {
index[--i] = lower;
}
loop: while (i > 0) {
do if (--lower < 0) {
center -= i;
arraycopy(index, i, index, 0, center);
break loop;
} while (isNaN(data.get(lower)));
index[--i] = lower;
}
/*
* La boucle suivante copie 'upper' et les index qui le suivent dans le tableau 'index'.
* Si on a atteint la fin des données sans avoir réussi à copier tous les index, on
* décalera vers la droite les index qui ont été copiés et on tentera de combler le trou
* créé à gauche en copiant d'autres index qui précédaient 'lower'.
*/
i = center;
index[i++] = upper;
loop: while (i < index.length) {
do if (++upper >= length) {
int remainder = index.length-i;
// center += remainder; // (not needed)
arraycopy(index, 0, index, remainder, i);
i = remainder;
do {
do if (--lower < 0) {
return false;
} while (isNaN(data.get(lower)));
index[--i] = lower;
} while (i > 0);
break loop;
} while (isNaN(data.get(upper)));
index[i++] = upper;
}
} else if (center > 0) {
index[0] = lower;
}
assert isSorted(index) : Arrays.toString(index);
return true;
}
/**
* {@inheritDoc}
*/
public double getInterval() {
int k0, k1;
/*
* Repère les index pointant vers les données à utiliser pour le calcul
* de l'intervalle. En l'absence de NaN on obtient:
*
* klo0 = lower khi0 = upper
* klo1 = klo0-1 khi1 = upper+1
*
* Le schema ci-dessous donne un exemple de la façon dont se comporte
* le code en la présence de NaN pour des 'lower' et 'upper' donnés.
*
* lower upper
* | |
* 140 145 150 NaN 160 165 170 NaN 180 185 190
* ^ ^ ^ ^
* k1 k0 k0 k1
*/
k0 = k1 = lower;
final int length = data.limit();
while (isNaN(data.get(k0))) {
if (++k0 >= length) {
return NaN;
}
}
do if (--k1 < 0) {
k1 = k0;
do if (++k0 >= length) {
return NaN;
} while (isNaN(data.get(k0)));
break;
} while (isNaN(data.get(k1)));
double x0 = data.get(k0);
final double xlo = (data.get(k1)-x0) / (k1-k0) * (lower-k0-0.5) + x0;
k0 = k1 = upper;
while (isNaN(data.get(k0))) {
if (--k0 < 0) {
return NaN;
}
}
do if (++k1 >= length) {
k1 = k0;
do if (--k0 < 0) {
return NaN;
} while (isNaN(data.get(k0)));
break;
}
while (isNaN(data.get(k1)));
x0 = data.get(k0);
return (data.get(k1)-x0) / (k1-k0)*(upper-k0+0.5)+x0 - xlo;
}
/**
* Retourne une copie de ce vecteur. Le buffer {@link #data} est copié (afin de pouvoir être
* utilisé dans un autre thread), mais pas les données qu'il contient. Cette copie reste donc
* relativement économique.
*/
@Override
public BufferedOrderedVector clone() {
final BufferedOrderedVector copy = (BufferedOrderedVector) super.clone();
final Field data;
try {
data = BufferedOrderedVector.class.getField("data");
} catch (NoSuchFieldException e) {
// Should never happen, since the field exists.
throw new AssertionError(e);
}
data.setAccessible(true);
try {
data.set(copy, copy.data.duplicate());
} catch (IllegalAccessException e) {
// Should never happen, since we made the field accessible.
throw new AssertionError(e);
}
data.setAccessible(false);
return copy;
}
}