diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 34d8bd85975..993d8e6452e 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -58,6 +58,9 @@ Optimizations * LUCENE-3298: FST can now be larger than 2.1 GB / 2.1 B nodes. (James Dyer, Mike McCandless) +* LUCENE-4690: Performance improvements and non-hashing versions + of NumericUtils.*ToPrefixCoded() (yonik) + New Features * LUCENE-4686: New specialized DGapVInt8IntEncoder for facets (now the @@ -70,9 +73,23 @@ New Features compresses term vectors into chunks of documents similarly to CompressingStoredFieldsFormat. (Adrien Grand) +* LUCENE-4695: Added LiveFieldValues utility class, for getting the + current (live, real-time) value for any indexed doc/field. The + class buffers recently indexed doc/field values until a new + near-real-time reader is opened that contains those changes. + (Robert Muir, Mike McCandless) + API Changes * LUCENE-4709: FacetResultNode no longer has a residue field. (Shai Erera) + +* LUCENE-4716: DrillDown.query now takes Occur, allowing to specify if + categories should be OR'ed or AND'ed. (Shai Erera) + +* LUCENE-4695: ReferenceManager.RefreshListener.afterRefresh now takes + a boolean indicating whether a new reference was in fact opened, and + a new beforeRefresh method notifies you when a refresh attempt is + starting. (Robert Muir, Mike McCandless) Bug Fixes @@ -414,6 +431,13 @@ Changes in Runtime Behavior This only affects requests with depth>1. If you execute such requests and rely on the facet results being returned flat (i.e. no hierarchy), you should set the ResultMode to GLOBAL_FLAT. (Shai Erera, Gilad Barkai) + +* LUCENE-1822: Improves the text window selection by recalculating the starting margin + once all phrases in the fragment have been identified in FastVectorHighlighter. This + way if a single word is matched in a fragment, it will appear in the middle of the highlight, + instead of 6 characters from the beginning. This way one can also guarantee that + the entirety of short texts are represented in a fragment by specifying a large + enough fragCharSize. Optimizations diff --git a/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java b/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java index 700258fdd28..c1c4648e0e2 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/compressing/CompressionMode.java @@ -45,7 +45,7 @@ public abstract class CompressionMode { @Override public Compressor newCompressor() { - return LZ4_FAST_COMPRESSOR; + return new LZ4FastCompressor(); } @Override @@ -95,7 +95,7 @@ public abstract class CompressionMode { @Override public Compressor newCompressor() { - return LZ4_HIGH_COMPRESSOR; + return new LZ4HighCompressor(); } @Override @@ -147,25 +147,37 @@ public abstract class CompressionMode { }; - private static final Compressor LZ4_FAST_COMPRESSOR = new Compressor() { + private static final class LZ4FastCompressor extends Compressor { + + private final LZ4.HashTable ht; + + LZ4FastCompressor() { + ht = new LZ4.HashTable(); + } @Override public void compress(byte[] bytes, int off, int len, DataOutput out) throws IOException { - LZ4.compress(bytes, off, len, out); + LZ4.compress(bytes, off, len, out, ht); } - }; + } - private static final Compressor LZ4_HIGH_COMPRESSOR = new Compressor() { + private static final class LZ4HighCompressor extends Compressor { + + private final LZ4.HCHashTable ht; + + LZ4HighCompressor() { + ht = new LZ4.HCHashTable(); + } @Override public void compress(byte[] bytes, int off, int len, DataOutput out) throws IOException { - LZ4.compressHC(bytes, off, len, out); + LZ4.compressHC(bytes, off, len, out, ht); } - }; + } private static final class DeflateDecompressor extends Decompressor { diff --git a/lucene/core/src/java/org/apache/lucene/codecs/compressing/LZ4.java b/lucene/core/src/java/org/apache/lucene/codecs/compressing/LZ4.java index 7e52339657d..022b58036ad 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/compressing/LZ4.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/compressing/LZ4.java @@ -30,7 +30,7 @@ import org.apache.lucene.util.packed.PackedInts; * http://code.google.com/p/lz4/ * http://fastcompression.blogspot.fr/p/lz4.html */ -class LZ4 { +final class LZ4 { private LZ4() {} @@ -181,11 +181,29 @@ class LZ4 { } } + static final class HashTable { + private int hashLog; + private PackedInts.Mutable hashTable; + + void reset(int len) { + final int bitsPerOffset = PackedInts.bitsRequired(len - LAST_LITERALS); + final int bitsPerOffsetLog = 32 - Integer.numberOfLeadingZeros(bitsPerOffset - 1); + hashLog = MEMORY_USAGE + 3 - bitsPerOffsetLog; + if (hashTable == null || hashTable.size() < 1 << hashLog || hashTable.getBitsPerValue() < bitsPerOffset) { + hashTable = PackedInts.getMutable(1 << hashLog, bitsPerOffset, PackedInts.DEFAULT); + } else { + hashTable.clear(); + } + } + + } + /** * Compress bytes[off:off+len] into out using - * at most 16KB of memory. + * at most 16KB of memory. ht shouldn't be shared across threads + * but can safely be reused. */ - public static void compress(byte[] bytes, int off, int len, DataOutput out) throws IOException { + public static void compress(byte[] bytes, int off, int len, DataOutput out, HashTable ht) throws IOException { final int base = off; final int end = off + len; @@ -196,11 +214,9 @@ class LZ4 { final int limit = end - LAST_LITERALS; final int matchLimit = limit - MIN_MATCH; - - final int bitsPerOffset = PackedInts.bitsRequired(len - LAST_LITERALS); - final int bitsPerOffsetLog = 32 - Integer.numberOfLeadingZeros(bitsPerOffset - 1); - final int hashLog = MEMORY_USAGE + 3 - bitsPerOffsetLog; - final PackedInts.Mutable hashTable = PackedInts.getMutable(1 << hashLog, bitsPerOffset, PackedInts.DEFAULT); + ht.reset(len); + final int hashLog = ht.hashLog; + final PackedInts.Mutable hashTable = ht.hashTable; main: while (off < limit) { @@ -256,20 +272,24 @@ class LZ4 { m2.ref = m1.ref; } - private static class HashTable { + static final class HCHashTable { static final int MAX_ATTEMPTS = 256; static final int MASK = MAX_DISTANCE - 1; int nextToUpdate; - private final int base; + private int base; private final int[] hashTable; private final short[] chainTable; - HashTable(int base) { + HCHashTable() { + hashTable = new int[HASH_TABLE_SIZE_HC]; + chainTable = new short[MAX_DISTANCE]; + } + + private void reset(int base) { this.base = base; nextToUpdate = base; - hashTable = new int[HASH_TABLE_SIZE_HC]; Arrays.fill(hashTable, -1); - chainTable = new short[MAX_DISTANCE]; + Arrays.fill(chainTable, (short) 0); } private int hashPointer(byte[] bytes, int off) { @@ -355,12 +375,14 @@ class LZ4 { /** * Compress bytes[off:off+len] into out. Compared to - * {@link LZ4#compress(byte[], int, int, DataOutput)}, this method is slower, - * uses more memory (~ 256KB), but should provide better compression ratios - * (especially on large inputs) because it chooses the best match among up to - * 256 candidates and then performs trade-offs to fix overlapping matches. + * {@link LZ4#compress(byte[], int, int, DataOutput, HashTable)}, this method + * is slower and uses more memory (~ 256KB per thread) but should provide + * better compression ratios (especially on large inputs) because it chooses + * the best match among up to 256 candidates and then performs trade-offs to + * fix overlapping matches. ht shouldn't be shared across threads + * but can safely be reused. */ - public static void compressHC(byte[] src, int srcOff, int srcLen, DataOutput out) throws IOException { + public static void compressHC(byte[] src, int srcOff, int srcLen, DataOutput out, HCHashTable ht) throws IOException { final int srcEnd = srcOff + srcLen; final int matchLimit = srcEnd - LAST_LITERALS; @@ -368,7 +390,7 @@ class LZ4 { int sOff = srcOff; int anchor = sOff++; - final HashTable ht = new HashTable(srcOff); + ht.reset(srcOff); final Match match0 = new Match(); final Match match1 = new Match(); final Match match2 = new Match(); diff --git a/lucene/core/src/java/org/apache/lucene/search/LiveFieldValues.java b/lucene/core/src/java/org/apache/lucene/search/LiveFieldValues.java new file mode 100644 index 00000000000..c0a28edf169 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/search/LiveFieldValues.java @@ -0,0 +1,133 @@ +package org.apache.lucene.search; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +import java.io.Closeable; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** Tracks live field values across NRT reader reopens. + * This holds a map for all updated ids since + * the last reader reopen. Once the NRT reader is reopened, + * it prunes the map. This means you must reopen your NRT + * reader periodically otherwise the RAM consumption of + * this class will grow unbounded! + * + *

NOTE: you must ensure the same id is never updated at + * the same time by two threads, because in this case you + * cannot in general know which thread "won". */ + +public abstract class LiveFieldValues implements ReferenceManager.RefreshListener, Closeable { + + private volatile Map current = new ConcurrentHashMap(); + private volatile Map old = new ConcurrentHashMap(); + private final ReferenceManager mgr; + private final T missingValue; + + public LiveFieldValues(ReferenceManager mgr, T missingValue) { + this.missingValue = missingValue; + this.mgr = mgr; + mgr.addListener(this); + } + + @Override + public void close() { + mgr.removeListener(this); + } + + @Override + public void beforeRefresh() throws IOException { + old = current; + // Start sending all updates after this point to the new + // map. While reopen is running, any lookup will first + // try this new map, then fallback to old, then to the + // current searcher: + current = new ConcurrentHashMap(); + } + + @Override + public void afterRefresh(boolean didRefresh) throws IOException { + // Now drop all the old values because they are now + // visible via the searcher that was just opened; if + // didRefresh is false, it's possible old has some + // entries in it, which is fine: it means they were + // actually already included in the previously opened + // reader. So we can safely clear old here: + old = new ConcurrentHashMap(); + } + + /** Call this after you've successfully added a document + * to the index, to record what value you just set the + * field to. */ + public void add(String id, T value) { + current.put(id, value); + } + + /** Call this after you've successfully deleted a document + * from the index. */ + public void delete(String id) { + current.put(id, missingValue); + } + + /** Returns the [approximate] number of id/value pairs + * buffered in RAM. */ + public int size() { + return current.size() + old.size(); + } + + /** Returns the current value for this id, or null if the + * id isn't in the index or was deleted. */ + public T get(String id) throws IOException { + // First try to get the "live" value: + T value = current.get(id); + if (value == missingValue) { + // Deleted but the deletion is not yet reflected in + // the reader: + return null; + } else if (value != null) { + return value; + } else { + value = old.get(id); + if (value == missingValue) { + // Deleted but the deletion is not yet reflected in + // the reader: + return null; + } else if (value != null) { + return value; + } else { + // It either does not exist in the index, or, it was + // already flushed & NRT reader was opened on the + // segment, so fallback to current searcher: + IndexSearcher s = mgr.acquire(); + try { + return lookupFromSearcher(s, id); + } finally { + mgr.release(s); + } + } + } + } + + /** This is called when the id/value was already flushed & opened + * in an NRT IndexSearcher. You must implement this to + * go look up the value (eg, via doc values, field cache, + * stored fields, etc.). */ + protected abstract T lookupFromSearcher(IndexSearcher s, String id) throws IOException; +} + diff --git a/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java b/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java index 1973f0583cd..62a7fc3d6e0 100755 --- a/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java +++ b/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java @@ -151,6 +151,7 @@ public abstract class ReferenceManager implements Closeable { try { final G reference = acquire(); try { + notifyRefreshListenersBefore(); G newReference = refreshIfNeeded(reference); if (newReference != null) { assert newReference != reference : "refreshIfNeeded should return null if refresh wasn't needed"; @@ -165,11 +166,9 @@ public abstract class ReferenceManager implements Closeable { } } finally { release(reference); + notifyRefreshListenersRefreshed(refreshed); } afterMaybeRefresh(); - if (refreshed) { - notifyRefreshListeners(); - } } finally { refreshLock.unlock(); } @@ -254,9 +253,15 @@ public abstract class ReferenceManager implements Closeable { decRef(reference); } - private void notifyRefreshListeners() { + private void notifyRefreshListenersBefore() throws IOException { for (RefreshListener refreshListener : refreshListeners) { - refreshListener.afterRefresh(); + refreshListener.beforeRefresh(); + } + } + + private void notifyRefreshListenersRefreshed(boolean didRefresh) throws IOException { + for (RefreshListener refreshListener : refreshListeners) { + refreshListener.afterRefresh(didRefresh); } } @@ -284,9 +289,13 @@ public abstract class ReferenceManager implements Closeable { * finished. See {@link #addListener}. */ public interface RefreshListener { - /** - * Called after a successful refresh and a new reference has been installed. When this is called {@link #acquire()} is guaranteed to return a new instance. - */ - void afterRefresh(); + /** Called right before a refresh attempt starts. */ + void beforeRefresh() throws IOException; + + /** Called after the attempted refresh; if the refresh + * did open a new reference then didRefresh will be true + * and {@link #acquire()} is guaranteed to return the new + * reference. */ + void afterRefresh(boolean didRefresh) throws IOException; } } diff --git a/lucene/core/src/java/org/apache/lucene/util/NumericUtils.java b/lucene/core/src/java/org/apache/lucene/util/NumericUtils.java index 0815ecb73de..f4fcc632339 100644 --- a/lucene/core/src/java/org/apache/lucene/util/NumericUtils.java +++ b/lucene/core/src/java/org/apache/lucene/util/NumericUtils.java @@ -82,7 +82,7 @@ public final class NumericUtils { /** * The maximum term length (used for byte[] buffer size) * for encoding long values. - * @see #longToPrefixCoded(long,int,BytesRef) + * @see #longToPrefixCodedBytes */ public static final int BUF_SIZE_LONG = 63/7 + 2; @@ -95,7 +95,7 @@ public final class NumericUtils { /** * The maximum term length (used for byte[] buffer size) * for encoding int values. - * @see #intToPrefixCoded(int,int,BytesRef) + * @see #intToPrefixCodedBytes */ public static final int BUF_SIZE_INT = 31/7 + 2; @@ -109,15 +109,42 @@ public final class NumericUtils { * @return the hash code for indexing (TermsHash) */ public static int longToPrefixCoded(final long val, final int shift, final BytesRef bytes) { - if (shift>63 || shift<0) + longToPrefixCodedBytes(val, shift, bytes); + return bytes.hashCode(); + } + + /** + * Returns prefix coded bits after reducing the precision by shift bits. + * This is method is used by {@link NumericTokenStream}. + * After encoding, {@code bytes.offset} will always be 0. + * @param val the numeric value + * @param shift how many bits to strip from the right + * @param bytes will contain the encoded value + * @return the hash code for indexing (TermsHash) + */ + public static int intToPrefixCoded(final int val, final int shift, final BytesRef bytes) { + intToPrefixCodedBytes(val, shift, bytes); + return bytes.hashCode(); + } + + /** + * Returns prefix coded bits after reducing the precision by shift bits. + * This is method is used by {@link NumericTokenStream}. + * After encoding, {@code bytes.offset} will always be 0. + * @param val the numeric value + * @param shift how many bits to strip from the right + * @param bytes will contain the encoded value + */ + public static void longToPrefixCodedBytes(final long val, final int shift, final BytesRef bytes) { + if ((shift & ~0x3f) != 0) // ensure shift is 0..63 throw new IllegalArgumentException("Illegal shift value, must be 0..63"); - int hash, nChars = (63-shift)/7 + 1; + int nChars = (((63-shift)*37)>>8) + 1; // i/7 is the same as (i*37)>>8 for i in 0..63 bytes.offset = 0; - bytes.length = nChars+1; + bytes.length = nChars+1; // one extra for the byte that contains the shift info if (bytes.bytes.length < bytes.length) { - bytes.grow(NumericUtils.BUF_SIZE_LONG); + bytes.bytes = new byte[NumericUtils.BUF_SIZE_LONG]; // use the max } - bytes.bytes[0] = (byte) (hash = (SHIFT_START_LONG + shift)); + bytes.bytes[0] = (byte)(SHIFT_START_LONG + shift); long sortableBits = val ^ 0x8000000000000000L; sortableBits >>>= shift; while (nChars > 0) { @@ -126,13 +153,9 @@ public final class NumericUtils { bytes.bytes[nChars--] = (byte)(sortableBits & 0x7f); sortableBits >>>= 7; } - // calculate hash - for (int i = 1; i < bytes.length; i++) { - hash = 31*hash + bytes.bytes[i]; - } - return hash; } + /** * Returns prefix coded bits after reducing the precision by shift bits. * This is method is used by {@link NumericTokenStream}. @@ -140,18 +163,17 @@ public final class NumericUtils { * @param val the numeric value * @param shift how many bits to strip from the right * @param bytes will contain the encoded value - * @return the hash code for indexing (TermsHash) */ - public static int intToPrefixCoded(final int val, final int shift, final BytesRef bytes) { - if (shift>31 || shift<0) + public static void intToPrefixCodedBytes(final int val, final int shift, final BytesRef bytes) { + if ((shift & ~0x1f) != 0) // ensure shift is 0..31 throw new IllegalArgumentException("Illegal shift value, must be 0..31"); - int hash, nChars = (31-shift)/7 + 1; + int nChars = (((31-shift)*37)>>8) + 1; // i/7 is the same as (i*37)>>8 for i in 0..63 bytes.offset = 0; - bytes.length = nChars+1; + bytes.length = nChars+1; // one extra for the byte that contains the shift info if (bytes.bytes.length < bytes.length) { - bytes.grow(NumericUtils.BUF_SIZE_INT); + bytes.bytes = new byte[NumericUtils.BUF_SIZE_LONG]; // use the max } - bytes.bytes[0] = (byte) (hash = (SHIFT_START_INT + shift)); + bytes.bytes[0] = (byte)(SHIFT_START_INT + shift); int sortableBits = val ^ 0x80000000; sortableBits >>>= shift; while (nChars > 0) { @@ -160,13 +182,9 @@ public final class NumericUtils { bytes.bytes[nChars--] = (byte)(sortableBits & 0x7f); sortableBits >>>= 7; } - // calculate hash - for (int i = 1; i < bytes.length; i++) { - hash = 31*hash + bytes.bytes[i]; - } - return hash; } + /** * Returns the shift value from a prefix encoded {@code long}. * @throws NumberFormatException if the supplied {@link BytesRef} is @@ -197,7 +215,7 @@ public final class NumericUtils { * This method can be used to decode a term's value. * @throws NumberFormatException if the supplied {@link BytesRef} is * not correctly prefix encoded. - * @see #longToPrefixCoded(long,int,BytesRef) + * @see #longToPrefixCodedBytes */ public static long prefixCodedToLong(final BytesRef val) { long sortableBits = 0L; @@ -221,7 +239,7 @@ public final class NumericUtils { * This method can be used to decode a term's value. * @throws NumberFormatException if the supplied {@link BytesRef} is * not correctly prefix encoded. - * @see #intToPrefixCoded(int,int,BytesRef) + * @see #intToPrefixCodedBytes */ public static int prefixCodedToInt(final BytesRef val) { int sortableBits = 0; @@ -402,8 +420,8 @@ public final class NumericUtils { */ public void addRange(final long min, final long max, final int shift) { final BytesRef minBytes = new BytesRef(BUF_SIZE_LONG), maxBytes = new BytesRef(BUF_SIZE_LONG); - longToPrefixCoded(min, shift, minBytes); - longToPrefixCoded(max, shift, maxBytes); + longToPrefixCodedBytes(min, shift, minBytes); + longToPrefixCodedBytes(max, shift, maxBytes); addRange(minBytes, maxBytes); } @@ -431,8 +449,8 @@ public final class NumericUtils { */ public void addRange(final int min, final int max, final int shift) { final BytesRef minBytes = new BytesRef(BUF_SIZE_INT), maxBytes = new BytesRef(BUF_SIZE_INT); - intToPrefixCoded(min, shift, minBytes); - intToPrefixCoded(max, shift, maxBytes); + intToPrefixCodedBytes(min, shift, minBytes); + intToPrefixCodedBytes(max, shift, maxBytes); addRange(minBytes, maxBytes); } diff --git a/lucene/core/src/test/org/apache/lucene/search/TestLiveFieldValues.java b/lucene/core/src/test/org/apache/lucene/search/TestLiveFieldValues.java new file mode 100644 index 00000000000..d13f148ceac --- /dev/null +++ b/lucene/core/src/test/org/apache/lucene/search/TestLiveFieldValues.java @@ -0,0 +1,180 @@ +package org.apache.lucene.search; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.IntField; +import org.apache.lucene.document.StringField; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.index.StoredDocument; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.NRTManager.TrackingIndexWriter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util._TestUtil; + +public class TestLiveFieldValues extends LuceneTestCase { + public void test() throws Exception { + + Directory dir = newFSDirectory(_TestUtil.getTempDir("livefieldupdates")); + IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random())); + + final IndexWriter _w = new IndexWriter(dir, iwc); + final TrackingIndexWriter w = new TrackingIndexWriter(_w); + + final NRTManager mgr = new NRTManager(w, new SearcherFactory() { + @Override + public IndexSearcher newSearcher(IndexReader r) { + return new IndexSearcher(r); + } + }); + + final Integer missing = -1; + + final LiveFieldValues rt = new LiveFieldValues(mgr, missing) { + @Override + protected Integer lookupFromSearcher(IndexSearcher s, String id) throws IOException { + TermQuery tq = new TermQuery(new Term("id", id)); + TopDocs hits = s.search(tq, 1); + assertTrue(hits.totalHits <= 1); + if (hits.totalHits == 0) { + return null; + } else { + StoredDocument doc = s.doc(hits.scoreDocs[0].doc); + return (Integer) doc.getField("field").numericValue(); + } + } + }; + + int numThreads = _TestUtil.nextInt(random(), 2, 5); + if (VERBOSE) { + System.out.println(numThreads + " threads"); + } + + final CountDownLatch startingGun = new CountDownLatch(1); + List threads = new ArrayList(); + + final int iters = atLeast(1000); + final int idCount = _TestUtil.nextInt(random(), 100, 10000); + + final double reopenChance = random().nextDouble()*0.01; + final double deleteChance = random().nextDouble()*0.25; + final double addChance = random().nextDouble()*0.5; + + for(int t=0;t values = new HashMap(); + List allIDs = Collections.synchronizedList(new ArrayList()); + + startingGun.await(); + for(int iter=0; iter 0 && threadRandom.nextDouble() <= deleteChance) { + String randomID = allIDs.get(threadRandom.nextInt(allIDs.size())); + w.deleteDocuments(new Term("id", randomID)); + rt.delete(randomID); + values.put(randomID, missing); + } + + if (threadRandom.nextDouble() <= reopenChance || rt.size() > 10000) { + //System.out.println("refresh @ " + rt.size()); + mgr.maybeRefresh(); + if (VERBOSE) { + IndexSearcher s = mgr.acquire(); + try { + System.out.println("TEST: reopen " + s); + } finally { + mgr.release(s); + } + System.out.println("TEST: " + values.size() + " values"); + } + } + + if (threadRandom.nextInt(10) == 7) { + assertEquals(null, rt.get("foo")); + } + + if (allIDs.size() > 0) { + String randomID = allIDs.get(threadRandom.nextInt(allIDs.size())); + Integer expected = values.get(randomID); + if (expected == missing) { + expected = null; + } + assertEquals("id=" + randomID, expected, rt.get(randomID)); + } + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }; + threads.add(thread); + thread.start(); + } + + startingGun.countDown(); + + for(Thread thread : threads) { + thread.join(); + } + mgr.maybeRefresh(); + assertEquals(0, rt.size()); + + rt.close(); + mgr.close(); + _w.close(); + dir.close(); + } +} diff --git a/lucene/core/src/test/org/apache/lucene/search/TestNRTManager.java b/lucene/core/src/test/org/apache/lucene/search/TestNRTManager.java index 38cc749051c..ded7c9d9159 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestNRTManager.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestNRTManager.java @@ -423,8 +423,13 @@ public class TestNRTManager extends ThreadedIndexingAndSearchingTestCase { NRTManager sm = new NRTManager(new NRTManager.TrackingIndexWriter(iw),new SearcherFactory()); sm.addListener(new ReferenceManager.RefreshListener() { @Override - public void afterRefresh() { - afterRefreshCalled.set(true); + public void beforeRefresh() { + } + @Override + public void afterRefresh(boolean didRefresh) { + if (didRefresh) { + afterRefreshCalled.set(true); + } } }); iw.addDocument(new Document()); diff --git a/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery32.java b/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery32.java index 6711f7add70..4ce557524f3 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery32.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery32.java @@ -380,8 +380,8 @@ public class TestNumericRangeQuery32 extends LuceneTestCase { int a=lower; lower=upper; upper=a; } final BytesRef lowerBytes = new BytesRef(NumericUtils.BUF_SIZE_INT), upperBytes = new BytesRef(NumericUtils.BUF_SIZE_INT); - NumericUtils.intToPrefixCoded(lower, 0, lowerBytes); - NumericUtils.intToPrefixCoded(upper, 0, upperBytes); + NumericUtils.intToPrefixCodedBytes(lower, 0, lowerBytes); + NumericUtils.intToPrefixCodedBytes(upper, 0, upperBytes); // test inclusive range NumericRangeQuery tq=NumericRangeQuery.newIntRange(field, precisionStep, lower, upper, true, true); diff --git a/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery64.java b/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery64.java index ede30d9ed44..648c7c75516 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery64.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestNumericRangeQuery64.java @@ -405,8 +405,8 @@ public class TestNumericRangeQuery64 extends LuceneTestCase { long a=lower; lower=upper; upper=a; } final BytesRef lowerBytes = new BytesRef(NumericUtils.BUF_SIZE_LONG), upperBytes = new BytesRef(NumericUtils.BUF_SIZE_LONG); - NumericUtils.longToPrefixCoded(lower, 0, lowerBytes); - NumericUtils.longToPrefixCoded(upper, 0, upperBytes); + NumericUtils.longToPrefixCodedBytes(lower, 0, lowerBytes); + NumericUtils.longToPrefixCodedBytes(upper, 0, upperBytes); // test inclusive range NumericRangeQuery tq=NumericRangeQuery.newLongRange(field, precisionStep, lower, upper, true, true); diff --git a/lucene/core/src/test/org/apache/lucene/search/TestSearcherManager.java b/lucene/core/src/test/org/apache/lucene/search/TestSearcherManager.java index 5a0cd46e0cc..9306f0a9d9b 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestSearcherManager.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestSearcherManager.java @@ -331,8 +331,13 @@ public class TestSearcherManager extends ThreadedIndexingAndSearchingTestCase { SearcherManager sm = new SearcherManager(iw, false, new SearcherFactory()); sm.addListener(new ReferenceManager.RefreshListener() { @Override - public void afterRefresh() { - afterRefreshCalled.set(true); + public void beforeRefresh() { + } + @Override + public void afterRefresh(boolean didRefresh) { + if (didRefresh) { + afterRefreshCalled.set(true); + } } }); iw.addDocument(new Document()); diff --git a/lucene/core/src/test/org/apache/lucene/util/TestNumericUtils.java b/lucene/core/src/test/org/apache/lucene/util/TestNumericUtils.java index 9153a1b0db9..125fd6d27a0 100644 --- a/lucene/core/src/test/org/apache/lucene/util/TestNumericUtils.java +++ b/lucene/core/src/test/org/apache/lucene/util/TestNumericUtils.java @@ -28,7 +28,7 @@ public class TestNumericUtils extends LuceneTestCase { // generate a series of encoded longs, each numerical one bigger than the one before BytesRef last=null, act=new BytesRef(NumericUtils.BUF_SIZE_LONG); for (long l=-100000L; l<100000L; l++) { - NumericUtils.longToPrefixCoded(l, 0, act); + NumericUtils.longToPrefixCodedBytes(l, 0, act); if (last!=null) { // test if smaller assertTrue("actual bigger than last (BytesRef)", last.compareTo(act) < 0 ); @@ -46,7 +46,7 @@ public class TestNumericUtils extends LuceneTestCase { // generate a series of encoded ints, each numerical one bigger than the one before BytesRef last=null, act=new BytesRef(NumericUtils.BUF_SIZE_INT); for (int i=-100000; i<100000; i++) { - NumericUtils.intToPrefixCoded(i, 0, act); + NumericUtils.intToPrefixCodedBytes(i, 0, act); if (last!=null) { // test if smaller assertTrue("actual bigger than last (BytesRef)", last.compareTo(act) < 0 ); @@ -69,7 +69,7 @@ public class TestNumericUtils extends LuceneTestCase { for (int i=0; i(); + dirsPerPartitionSize = new HashMap(); } @AfterClass @@ -181,8 +180,10 @@ public abstract class FacetTestBase extends LuceneTestCase { return newIndexWriterConfig(TEST_VERSION_CURRENT, analyzer); } - /** Returns a default facet indexing params */ + /** Returns a {@link FacetIndexingParams} per the given partition size. */ protected FacetIndexingParams getFacetIndexingParams(final int partSize) { + // several of our encoders don't support the value 0, + // which is one of the values encoded when dealing w/ partitions. return new FacetIndexingParams() { @Override public int getPartitionSize() { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/FacetTestCase.java b/lucene/facet/src/test/org/apache/lucene/facet/FacetTestCase.java new file mode 100644 index 00000000000..a6cf3b8ec3b --- /dev/null +++ b/lucene/facet/src/test/org/apache/lucene/facet/FacetTestCase.java @@ -0,0 +1,64 @@ +package org.apache.lucene.facet; + +import java.util.Random; + +import org.apache.lucene.facet.index.params.CategoryListParams; +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util.encoding.DGapIntEncoder; +import org.apache.lucene.util.encoding.DGapVInt8IntEncoder; +import org.apache.lucene.util.encoding.EightFlagsIntEncoder; +import org.apache.lucene.util.encoding.FourFlagsIntEncoder; +import org.apache.lucene.util.encoding.IntEncoder; +import org.apache.lucene.util.encoding.NOnesIntEncoder; +import org.apache.lucene.util.encoding.SortingIntEncoder; +import org.apache.lucene.util.encoding.UniqueValuesIntEncoder; +import org.apache.lucene.util.encoding.VInt8IntEncoder; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +public class FacetTestCase extends LuceneTestCase { + + private static final IntEncoder[] ENCODERS = new IntEncoder[] { + new SortingIntEncoder(new UniqueValuesIntEncoder(new VInt8IntEncoder())), + new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new VInt8IntEncoder()))), + new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapVInt8IntEncoder())), + new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new EightFlagsIntEncoder()))), + new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new FourFlagsIntEncoder()))), + new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new NOnesIntEncoder(3)))), + new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new NOnesIntEncoder(4)))), + }; + + /** Returns a {@link CategoryListParams} with random {@link IntEncoder} and field. */ + public static CategoryListParams randomCategoryListParams() { + final String field = CategoryListParams.DEFAULT_FIELD + "$" + random().nextInt(); + return randomCategoryListParams(field); + } + + /** Returns a {@link CategoryListParams} with random {@link IntEncoder}. */ + public static CategoryListParams randomCategoryListParams(String field) { + Random random = random(); + final IntEncoder encoder = ENCODERS[random.nextInt(ENCODERS.length)]; + return new CategoryListParams(field) { + @Override + public IntEncoder createEncoder() { + return encoder; + } + }; + } + +} diff --git a/lucene/facet/src/test/org/apache/lucene/facet/FacetTestUtils.java b/lucene/facet/src/test/org/apache/lucene/facet/FacetTestUtils.java index 2c8f0f323e4..7846f154e25 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/FacetTestUtils.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/FacetTestUtils.java @@ -1,30 +1,17 @@ package org.apache.lucene.facet; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import org.apache.lucene.analysis.MockAnalyzer; -import org.apache.lucene.facet.index.params.FacetIndexingParams; -import org.apache.lucene.facet.search.FacetsCollector; -import org.apache.lucene.facet.search.params.CountFacetRequest; -import org.apache.lucene.facet.search.params.FacetRequest; -import org.apache.lucene.facet.search.params.FacetSearchParams; import org.apache.lucene.facet.search.results.FacetResult; import org.apache.lucene.facet.search.results.FacetResultNode; -import org.apache.lucene.facet.taxonomy.CategoryPath; -import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.TaxonomyWriter; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.search.Collector; import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.MatchAllDocsQuery; -import org.apache.lucene.search.MultiCollector; -import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.util.LuceneTestCase; @@ -109,30 +96,6 @@ public class FacetTestUtils { return pairs; } - public static Collector[] search(IndexSearcher searcher, TaxonomyReader taxonomyReader, FacetIndexingParams iParams, - int k, String... facetNames) throws IOException { - - Collector[] collectors = new Collector[2]; - - List fRequests = new ArrayList(); - for (String facetName : facetNames) { - CategoryPath cp = new CategoryPath(facetName); - FacetRequest fq = new CountFacetRequest(cp, k); - fRequests.add(fq); - } - FacetSearchParams facetSearchParams = new FacetSearchParams(fRequests, iParams); - - TopScoreDocCollector topDocsCollector = TopScoreDocCollector.create(searcher.getIndexReader().maxDoc(), true); - FacetsCollector facetsCollector = FacetsCollector.create(facetSearchParams, searcher.getIndexReader(), taxonomyReader); - Collector mColl = MultiCollector.wrap(topDocsCollector, facetsCollector); - - collectors[0] = topDocsCollector; - collectors[1] = facetsCollector; - - searcher.search(new MatchAllDocsQuery(), mColl); - return collectors; - } - public static String toSimpleString(FacetResult fr) { StringBuilder sb = new StringBuilder(); toSimpleString(0, sb, fr.getFacetResultNode(), ""); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/index/OrdinalMappingReaderTest.java b/lucene/facet/src/test/org/apache/lucene/facet/index/OrdinalMappingReaderTest.java index 9ec237f424a..8f311f0991f 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/index/OrdinalMappingReaderTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/index/OrdinalMappingReaderTest.java @@ -7,16 +7,9 @@ import java.util.List; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.document.Document; -import org.apache.lucene.index.DirectoryReader; -import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.index.RandomIndexWriter; -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.MatchAllDocsQuery; -import org.apache.lucene.store.Directory; -import org.junit.Test; - -import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.example.merge.TaxonomyMergeUtils; +import org.apache.lucene.facet.index.params.FacetIndexingParams; import org.apache.lucene.facet.search.FacetsCollector; import org.apache.lucene.facet.search.params.CountFacetRequest; import org.apache.lucene.facet.search.params.FacetSearchParams; @@ -25,6 +18,13 @@ import org.apache.lucene.facet.search.results.FacetResultNode; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.store.Directory; +import org.junit.Test; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -43,34 +43,35 @@ import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; * limitations under the License. */ -public class OrdinalMappingReaderTest extends LuceneTestCase { +public class OrdinalMappingReaderTest extends FacetTestCase { private static final int NUM_DOCS = 100; @Test public void testTaxonomyMergeUtils() throws Exception { Directory dir = newDirectory(); - Directory taxDir = newDirectory(); - buildIndexWithFacets(dir, taxDir, true); + Directory taxDir = newDirectory(); + FacetIndexingParams fip = new FacetIndexingParams(randomCategoryListParams()); + buildIndexWithFacets(dir, taxDir, true, fip); Directory dir1 = newDirectory(); Directory taxDir1 = newDirectory(); - buildIndexWithFacets(dir1, taxDir1, false); + buildIndexWithFacets(dir1, taxDir1, false, fip); - TaxonomyMergeUtils.merge(dir, taxDir, dir1, taxDir1); + TaxonomyMergeUtils.merge(dir, taxDir, dir1, taxDir1, fip); - verifyResults(dir1, taxDir1); + verifyResults(dir1, taxDir1, fip); dir1.close(); taxDir1.close(); dir.close(); taxDir.close(); } - private void verifyResults(Directory dir, Directory taxDir) throws IOException { + private void verifyResults(Directory dir, Directory taxDir, FacetIndexingParams fip) throws IOException { DirectoryReader reader1 = DirectoryReader.open(dir); DirectoryTaxonomyReader taxReader = new DirectoryTaxonomyReader(taxDir); IndexSearcher searcher = newSearcher(reader1); - FacetSearchParams fsp = new FacetSearchParams(new CountFacetRequest(new CategoryPath("tag"), NUM_DOCS)); + FacetSearchParams fsp = new FacetSearchParams(fip, new CountFacetRequest(new CategoryPath("tag"), NUM_DOCS)); FacetsCollector collector = FacetsCollector.create(fsp, reader1, taxReader); searcher.search(new MatchAllDocsQuery(), collector); FacetResult result = collector.getFacetResults().get(0); @@ -88,7 +89,7 @@ public class OrdinalMappingReaderTest extends LuceneTestCase { taxReader.close(); } - private void buildIndexWithFacets(Directory dir, Directory taxDir, boolean asc) throws IOException { + private void buildIndexWithFacets(Directory dir, Directory taxDir, boolean asc, FacetIndexingParams fip) throws IOException { IndexWriterConfig config = newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false)); RandomIndexWriter writer = new RandomIndexWriter(random(), dir, config); @@ -101,7 +102,7 @@ public class OrdinalMappingReaderTest extends LuceneTestCase { int facetValue = asc? j: NUM_DOCS - j; categoryPaths.add(new CategoryPath("tag", Integer.toString(facetValue))); } - FacetFields facetFields = new FacetFields(taxonomyWriter); + FacetFields facetFields = new FacetFields(taxonomyWriter, fip); facetFields.addFields(doc, categoryPaths); writer.addDocument(doc); } diff --git a/lucene/facet/src/test/org/apache/lucene/facet/index/TestFacetsPayloadMigrationReader.java b/lucene/facet/src/test/org/apache/lucene/facet/index/TestFacetsPayloadMigrationReader.java index c9b14593d2e..f45aba5c4f3 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/index/TestFacetsPayloadMigrationReader.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/index/TestFacetsPayloadMigrationReader.java @@ -21,6 +21,7 @@ import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.FieldType; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.params.CategoryListParams; import org.apache.lucene.facet.index.params.FacetIndexingParams; import org.apache.lucene.facet.index.params.PerDimensionIndexingParams; @@ -57,11 +58,11 @@ import org.apache.lucene.search.MultiCollector; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TotalHitCountCollector; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IntsRef; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -82,7 +83,7 @@ import org.junit.Test; */ /** Tests facets index migration from payload to DocValues.*/ -public class TestFacetsPayloadMigrationReader extends LuceneTestCase { +public class TestFacetsPayloadMigrationReader extends FacetTestCase { private static class PayloadFacetFields extends FacetFields { @@ -284,7 +285,7 @@ public class TestFacetsPayloadMigrationReader extends LuceneTestCase { for (String dim : expectedCounts.keySet()) { CategoryPath drillDownCP = new CategoryPath(dim); FacetSearchParams fsp = new FacetSearchParams(fip, new CountFacetRequest(drillDownCP, 10)); - Query drillDown = DrillDown.query(fsp, new MatchAllDocsQuery(), drillDownCP); + Query drillDown = DrillDown.query(fsp, new MatchAllDocsQuery(), Occur.MUST, drillDownCP); TotalHitCountCollector total = new TotalHitCountCollector(); FacetsCollector fc = FacetsCollector.create(fsp, indexReader, taxoReader); searcher.search(drillDown, MultiCollector.wrap(fc, total)); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/index/params/CategoryListParamsTest.java b/lucene/facet/src/test/org/apache/lucene/facet/index/params/CategoryListParamsTest.java index 4534b89ecba..49378e40672 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/index/params/CategoryListParamsTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/index/params/CategoryListParamsTest.java @@ -1,6 +1,6 @@ package org.apache.lucene.facet.index.params; -import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.util.encoding.DGapVInt8IntEncoder; import org.apache.lucene.util.encoding.IntDecoder; import org.apache.lucene.util.encoding.IntEncoder; @@ -25,7 +25,7 @@ import org.junit.Test; * limitations under the License. */ -public class CategoryListParamsTest extends LuceneTestCase { +public class CategoryListParamsTest extends FacetTestCase { @Test public void testDefaultSettings() { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/index/params/FacetIndexingParamsTest.java b/lucene/facet/src/test/org/apache/lucene/facet/index/params/FacetIndexingParamsTest.java index 5ab8d2f032e..f24fec84283 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/index/params/FacetIndexingParamsTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/index/params/FacetIndexingParamsTest.java @@ -1,10 +1,10 @@ package org.apache.lucene.facet.index.params; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.search.DrillDown; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.util.PartitionsUtils; import org.apache.lucene.index.Term; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -24,7 +24,7 @@ import org.junit.Test; * limitations under the License. */ -public class FacetIndexingParamsTest extends LuceneTestCase { +public class FacetIndexingParamsTest extends FacetTestCase { @Test public void testDefaultSettings() { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/index/params/PerDimensionIndexingParamsTest.java b/lucene/facet/src/test/org/apache/lucene/facet/index/params/PerDimensionIndexingParamsTest.java index 6db5e22b262..ef8ec97b336 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/index/params/PerDimensionIndexingParamsTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/index/params/PerDimensionIndexingParamsTest.java @@ -2,11 +2,11 @@ package org.apache.lucene.facet.index.params; import java.util.Collections; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.search.DrillDown; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.util.PartitionsUtils; import org.apache.lucene.index.Term; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -26,7 +26,7 @@ import org.junit.Test; * limitations under the License. */ -public class PerDimensionIndexingParamsTest extends LuceneTestCase { +public class PerDimensionIndexingParamsTest extends FacetTestCase { @Test public void testTopLevelSettings() { @@ -41,7 +41,6 @@ public class PerDimensionIndexingParamsTest extends LuceneTestCase { assertEquals("3 characters should be written", 3, numchars); assertEquals("wrong drill-down term text", expectedDDText, new String(buf, 0, numchars)); - CategoryListParams clParams = ifip.getCategoryListParams(null); assertEquals("partition for all ordinals is the first", "", PartitionsUtils.partitionNameByOrdinal(ifip, 250)); assertEquals("for partition 0, the same name should be returned", "", PartitionsUtils.partitionName(0)); assertEquals("for any other, it's the concatenation of name + partition", PartitionsUtils.PART_NAME_PREFIX + "1", PartitionsUtils.partitionName(1)); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/CategoryListIteratorTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/CategoryListIteratorTest.java index 26f1cd50507..a8d9d8bceef 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/CategoryListIteratorTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/CategoryListIteratorTest.java @@ -7,13 +7,13 @@ import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.document.BinaryDocValuesField; import org.apache.lucene.document.Document; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.IntsRef; -import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.encoding.DGapIntEncoder; import org.apache.lucene.util.encoding.IntEncoder; import org.apache.lucene.util.encoding.SortingIntEncoder; @@ -38,7 +38,7 @@ import org.junit.Test; * limitations under the License. */ -public class CategoryListIteratorTest extends LuceneTestCase { +public class CategoryListIteratorTest extends FacetTestCase { static final IntsRef[] data = new IntsRef[] { new IntsRef(new int[] { 1, 2 }, 0, 2), @@ -48,9 +48,9 @@ public class CategoryListIteratorTest extends LuceneTestCase { }; @Test - public void testPayloadCategoryListIteraor() throws Exception { + public void test() throws Exception { Directory dir = newDirectory(); - final IntEncoder encoder = new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new VInt8IntEncoder()))); + final IntEncoder encoder = randomCategoryListParams().createEncoder(); RandomIndexWriter writer = new RandomIndexWriter(random(), dir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random(), MockTokenizer.KEYWORD, false)).setMergePolicy(newLogMergePolicy())); BytesRef buf = new BytesRef(); @@ -89,7 +89,7 @@ public class CategoryListIteratorTest extends LuceneTestCase { } @Test - public void testPayloadIteratorWithInvalidDoc() throws Exception { + public void testEmptyDocuments() throws Exception { Directory dir = newDirectory(); final IntEncoder encoder = new SortingIntEncoder(new UniqueValuesIntEncoder(new DGapIntEncoder(new VInt8IntEncoder()))); // NOTE: test is wired to LogMP... because test relies on certain docids having payloads diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/CountingFacetsCollectorTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/CountingFacetsCollectorTest.java index 8ec20d38a29..3904483751e 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/CountingFacetsCollectorTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/CountingFacetsCollectorTest.java @@ -13,6 +13,7 @@ import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.StringField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.index.params.CategoryListParams; import org.apache.lucene.facet.index.params.FacetIndexingParams; @@ -40,7 +41,6 @@ import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.collections.ObjectToIntMap; import org.apache.lucene.util.encoding.IntEncoder; import org.apache.lucene.util.encoding.VInt8IntEncoder; @@ -65,7 +65,7 @@ import org.junit.Test; * limitations under the License. */ -public class CountingFacetsCollectorTest extends LuceneTestCase { +public class CountingFacetsCollectorTest extends FacetTestCase { private static final Term A = new Term("f", "a"); private static final CategoryPath CP_A = new CategoryPath("A"), CP_B = new CategoryPath("B"); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java index 9a881cbad20..16846d64842 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java @@ -10,6 +10,7 @@ import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.index.params.CategoryListParams; import org.apache.lucene.facet.index.params.FacetIndexingParams; @@ -26,8 +27,8 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.store.Directory; -import org.apache.lucene.util.LuceneTestCase; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -49,9 +50,9 @@ import org.junit.Test; * limitations under the License. */ -public class DrillDownTest extends LuceneTestCase { +public class DrillDownTest extends FacetTestCase { - private FacetIndexingParams defaultParams = FacetIndexingParams.ALL_PARENTS; + private FacetIndexingParams defaultParams; private PerDimensionIndexingParams nonDefaultParams; private static IndexReader reader; private static DirectoryTaxonomyReader taxo; @@ -60,9 +61,10 @@ public class DrillDownTest extends LuceneTestCase { public DrillDownTest() { Map paramsMap = new HashMap(); - paramsMap.put(new CategoryPath("a"), new CategoryListParams("testing_facets_a")); - paramsMap.put(new CategoryPath("b"), new CategoryListParams("testing_facets_b")); + paramsMap.put(new CategoryPath("a"), randomCategoryListParams("testing_facets_a")); + paramsMap.put(new CategoryPath("b"), randomCategoryListParams("testing_facets_b")); nonDefaultParams = new PerDimensionIndexingParams(paramsMap); + defaultParams = new FacetIndexingParams(randomCategoryListParams(CategoryListParams.DEFAULT_FIELD)); } @BeforeClass @@ -128,25 +130,25 @@ public class DrillDownTest extends LuceneTestCase { IndexSearcher searcher = newSearcher(reader); // Making sure the query yields 25 documents with the facet "a" - Query q = DrillDown.query(defaultParams, null, new CategoryPath("a")); + Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); TopDocs docs = searcher.search(q, 100); assertEquals(25, docs.totalHits); // Making sure the query yields 5 documents with the facet "b" and the // previous (facet "a") query as a base query - Query q2 = DrillDown.query(defaultParams, q, new CategoryPath("b")); + Query q2 = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("b")); docs = searcher.search(q2, 100); assertEquals(5, docs.totalHits); // Making sure that a query of both facet "a" and facet "b" yields 5 results - Query q3 = DrillDown.query(defaultParams, null, new CategoryPath("a"), new CategoryPath("b")); + Query q3 = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a"), new CategoryPath("b")); docs = searcher.search(q3, 100); assertEquals(5, docs.totalHits); // Check that content:foo (which yields 50% results) and facet/b (which yields 20%) // would gather together 10 results (10%..) Query fooQuery = new TermQuery(new Term("content", "foo")); - Query q4 = DrillDown.query(defaultParams, fooQuery, new CategoryPath("b")); + Query q4 = DrillDown.query(defaultParams, fooQuery, Occur.MUST, new CategoryPath("b")); docs = searcher.search(q4, 100); assertEquals(10, docs.totalHits); } @@ -156,18 +158,18 @@ public class DrillDownTest extends LuceneTestCase { IndexSearcher searcher = newSearcher(reader); // Create the base query to start with - Query q = DrillDown.query(defaultParams, null, new CategoryPath("a")); + Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); // Making sure the query yields 5 documents with the facet "b" and the // previous (facet "a") query as a base query - Query q2 = DrillDown.query(defaultParams, q, new CategoryPath("b")); + Query q2 = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("b")); TopDocs docs = searcher.search(q2, 100); assertEquals(5, docs.totalHits); // Check that content:foo (which yields 50% results) and facet/b (which yields 20%) // would gather together 10 results (10%..) Query fooQuery = new TermQuery(new Term("content", "foo")); - Query q4 = DrillDown.query(defaultParams, fooQuery, new CategoryPath("b")); + Query q4 = DrillDown.query(defaultParams, fooQuery, Occur.MUST, new CategoryPath("b")); docs = searcher.search(q4, 100); assertEquals(10, docs.totalHits); } @@ -202,7 +204,7 @@ public class DrillDownTest extends LuceneTestCase { } // create a drill-down query with category "a", scores should not change - q = DrillDown.query(defaultParams, q, new CategoryPath("a")); + q = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("a")); docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query for (ScoreDoc sd : docs.scoreDocs) { assertEquals("score of doc=" + sd.doc + " modified", scores[sd.doc], sd.score, 0f); @@ -214,11 +216,21 @@ public class DrillDownTest extends LuceneTestCase { // verify that drill-down queries (with no base query) returns 0.0 score IndexSearcher searcher = newSearcher(reader); - Query q = DrillDown.query(defaultParams, null, new CategoryPath("a")); + Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); TopDocs docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query for (ScoreDoc sd : docs.scoreDocs) { assertEquals(0f, sd.score, 0f); } } + + @Test + public void testOrQuery() throws Exception { + IndexSearcher searcher = newSearcher(reader); + + // Making sure that a query of facet "a" or facet "b" yields 0 results + Query q = DrillDown.query(defaultParams, null, Occur.SHOULD, new CategoryPath("a"), new CategoryPath("b")); + TopDocs docs = searcher.search(q, 100); + assertEquals(40, docs.totalHits); + } } diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java index 0331c1607ee..a4f53e7da0a 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import org.apache.lucene.document.Document; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.FacetTestUtils; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.search.params.CountFacetRequest; @@ -39,14 +40,14 @@ import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.store.Directory; -import org.apache.lucene.util.LuceneTestCase; -public class TestDemoFacets extends LuceneTestCase { +public class TestDemoFacets extends FacetTestCase { private DirectoryTaxonomyWriter taxoWriter; private RandomIndexWriter writer; - private FacetFields docBuilder; + private FacetFields facetFields; private void add(String ... categoryPaths) throws IOException { Document doc = new Document(); @@ -55,7 +56,7 @@ public class TestDemoFacets extends LuceneTestCase { for(String categoryPath : categoryPaths) { paths.add(new CategoryPath(categoryPath, '/')); } - docBuilder.addFields(doc, paths); + facetFields.addFields(doc, paths); writer.addDocument(doc); } @@ -70,7 +71,7 @@ public class TestDemoFacets extends LuceneTestCase { // Reused across documents, to add the necessary facet // fields: - docBuilder = new FacetFields(taxoWriter); + facetFields = new FacetFields(taxoWriter); add("Author/Bob", "Publish Date/2010/10/15"); add("Author/Lisa", "Publish Date/2010/10/20"); @@ -111,7 +112,7 @@ public class TestDemoFacets extends LuceneTestCase { // Now user drills down on Publish Date/2010: fsp = new FacetSearchParams(new CountFacetRequest(new CategoryPath("Author"), 10)); - Query q2 = DrillDown.query(fsp, new MatchAllDocsQuery(), new CategoryPath("Publish Date/2010", '/')); + Query q2 = DrillDown.query(fsp, new MatchAllDocsQuery(), Occur.MUST, new CategoryPath("Publish Date/2010", '/')); c = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader); searcher.search(q2, c); results = c.getFacetResults(); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetArrays.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetArrays.java index 05c19a807b8..8aab807a6d2 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetArrays.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetArrays.java @@ -1,6 +1,6 @@ package org.apache.lucene.facet.search; -import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; /* @@ -20,7 +20,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestFacetArrays extends LuceneTestCase { +public class TestFacetArrays extends FacetTestCase { @Test public void testFacetArrays() { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsAccumulatorWithComplement.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsAccumulatorWithComplement.java index ea7eec066e9..13fc3d18dcf 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsAccumulatorWithComplement.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsAccumulatorWithComplement.java @@ -123,15 +123,10 @@ public class TestFacetsAccumulatorWithComplement extends FacetTestBase { } - private FacetSearchParams getFacetSearchParams() { - return new FacetSearchParams(new CountFacetRequest(new CategoryPath("root","a"), 10)); - } - /** compute facets with certain facet requests and docs */ private List findFacets(ScoredDocIDs sDocids, boolean withComplement) throws IOException { - - FacetsAccumulator fAccumulator = - new StandardFacetsAccumulator(getFacetSearchParams(), indexReader, taxoReader); + FacetSearchParams fsp = new FacetSearchParams(getFacetIndexingParams(Integer.MAX_VALUE), new CountFacetRequest(new CategoryPath("root","a"), 10)); + FacetsAccumulator fAccumulator = new StandardFacetsAccumulator(fsp, indexReader, taxoReader); fAccumulator.setComplementThreshold( withComplement ? diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsCollector.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsCollector.java index 4a1e83d0db0..5c48be09b85 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsCollector.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestFacetsCollector.java @@ -7,6 +7,7 @@ import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.StringField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.search.params.FacetSearchParams; import org.apache.lucene.facet.search.params.ScoreFacetRequest; @@ -24,7 +25,6 @@ import org.apache.lucene.search.MultiCollector; import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -44,7 +44,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestFacetsCollector extends LuceneTestCase { +public class TestFacetsCollector extends FacetTestCase { @Test public void testFacetsWithDocScore() throws Exception { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestMultipleCategoryLists.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestMultipleCategoryLists.java index a9e5dc84435..a0da35d6616 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestMultipleCategoryLists.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestMultipleCategoryLists.java @@ -13,6 +13,7 @@ import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.FacetTestUtils; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.index.params.CategoryListParams; @@ -41,7 +42,6 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -61,7 +61,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestMultipleCategoryLists extends LuceneTestCase { +public class TestMultipleCategoryLists extends FacetTestCase { private static final CategoryPath[] CATEGORIES = new CategoryPath[] { new CategoryPath("Author", "Mark Twain"), diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestScoredDocIdCollector.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestScoredDocIdCollector.java index 0fc8b5522e7..bac10b19659 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestScoredDocIdCollector.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestScoredDocIdCollector.java @@ -4,24 +4,18 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import org.apache.lucene.index.Term; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.TermQuery; -import org.junit.Before; -import org.junit.Test; - import org.apache.lucene.facet.FacetTestBase; -import org.apache.lucene.facet.search.FacetsAccumulator; -import org.apache.lucene.facet.search.ScoredDocIDs; -import org.apache.lucene.facet.search.ScoredDocIDsIterator; -import org.apache.lucene.facet.search.ScoredDocIdCollector; -import org.apache.lucene.facet.search.StandardFacetsAccumulator; import org.apache.lucene.facet.search.params.CountFacetRequest; import org.apache.lucene.facet.search.params.FacetSearchParams; import org.apache.lucene.facet.search.params.ScoreFacetRequest; import org.apache.lucene.facet.search.results.FacetResult; import org.apache.lucene.facet.search.results.FacetResultNode; import org.apache.lucene.facet.taxonomy.CategoryPath; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.junit.Before; +import org.junit.Test; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -65,8 +59,7 @@ public class TestScoredDocIdCollector extends FacetTestBase { System.out.println("Query: " + q); } float constScore = 17.0f; - ScoredDocIdCollector dCollector = ScoredDocIdCollector.create(indexReader - .maxDoc(), false); // scoring is disabled + ScoredDocIdCollector dCollector = ScoredDocIdCollector.create(indexReader.maxDoc(), false); // scoring is disabled dCollector.setDefaultScore(constScore); searcher.search(q, dCollector); @@ -75,13 +68,16 @@ public class TestScoredDocIdCollector extends FacetTestBase { assertEquals("Wrong number of matching documents!", 2, scoredDocIDs.size()); ScoredDocIDsIterator docItr = scoredDocIDs.iterator(); while (docItr.next()) { - assertEquals("Wrong score for doc " + docItr.getDocID(), constScore, - docItr.getScore(), Double.MIN_VALUE); + assertEquals("Wrong score for doc " + docItr.getDocID(), constScore, docItr.getScore(), Double.MIN_VALUE); } // verify by facet values - List countRes = findFacets(scoredDocIDs, getFacetSearchParams()); - List scoreRes = findFacets(scoredDocIDs, sumScoreSearchParams()); + CategoryPath cp = new CategoryPath("root","a"); + FacetSearchParams countFSP = new FacetSearchParams(getFacetIndexingParams(Integer.MAX_VALUE), new CountFacetRequest(cp, 10)); + FacetSearchParams scoreFSP = new FacetSearchParams(getFacetIndexingParams(Integer.MAX_VALUE), new ScoreFacetRequest(cp, 10)); + + List countRes = findFacets(scoredDocIDs, countFSP); + List scoreRes = findFacets(scoredDocIDs, scoreFSP); assertEquals("Wrong number of facet count results!", 1, countRes.size()); assertEquals("Wrong number of facet score results!", 1, scoreRes.size()); @@ -151,14 +147,4 @@ public class TestScoredDocIdCollector extends FacetTestBase { } } - /* use a scoring aggregator */ - private FacetSearchParams sumScoreSearchParams() { - // this will use default faceted indexing params, not altering anything about indexing - return new FacetSearchParams(new ScoreFacetRequest(new CategoryPath("root", "a"), 10)); - } - - private FacetSearchParams getFacetSearchParams() { - return new FacetSearchParams(new CountFacetRequest(new CategoryPath("root","a"), 10)); - } - -} \ No newline at end of file +} diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestStandardFacetsAccumulator.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestStandardFacetsAccumulator.java index 50f1d171610..06e09480458 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestStandardFacetsAccumulator.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestStandardFacetsAccumulator.java @@ -8,6 +8,7 @@ import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.StringField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.index.params.CategoryListParams; import org.apache.lucene.facet.index.params.FacetIndexingParams; @@ -32,7 +33,6 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -52,7 +52,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestStandardFacetsAccumulator extends LuceneTestCase { +public class TestStandardFacetsAccumulator extends FacetTestCase { private void indexTwoDocs(IndexWriter indexWriter, FacetFields facetFields, boolean withContent) throws Exception { for (int i = 0; i < 2; i++) { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestTopKInEachNodeResultHandler.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestTopKInEachNodeResultHandler.java index 7a3f3af2897..856e36f94a4 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestTopKInEachNodeResultHandler.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestTopKInEachNodeResultHandler.java @@ -9,6 +9,7 @@ import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.index.params.FacetIndexingParams; import org.apache.lucene.facet.search.params.CountFacetRequest; @@ -30,7 +31,6 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -50,7 +50,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestTopKInEachNodeResultHandler extends LuceneTestCase { +public class TestTopKInEachNodeResultHandler extends FacetTestCase { //TODO (Facet): Move to extend BaseTestTopK and separate to several smaller test cases (methods) - see TestTopKResultsHandler diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCounts.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCounts.java index 7dfd8c6c0de..82388b7fabb 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCounts.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCounts.java @@ -4,13 +4,13 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.FacetTestUtils; import org.apache.lucene.facet.FacetTestUtils.IndexTaxonomyReaderPair; import org.apache.lucene.facet.FacetTestUtils.IndexTaxonomyWriterPair; import org.apache.lucene.facet.index.params.FacetIndexingParams; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util._TestUtil; import org.junit.Test; @@ -31,7 +31,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestTotalFacetCounts extends LuceneTestCase { +public class TestTotalFacetCounts extends FacetTestCase { private static void initCache(int numEntries) { TotalFacetCountsCache.getSingleton().clear(); @@ -53,8 +53,7 @@ public class TestTotalFacetCounts extends LuceneTestCase { // Create temporary RAMDirectories Directory[][] dirs = FacetTestUtils.createIndexTaxonomyDirs(1); // Create our index/taxonomy writers - IndexTaxonomyWriterPair[] writers = FacetTestUtils - .createIndexTaxonomyWriterPair(dirs); + IndexTaxonomyWriterPair[] writers = FacetTestUtils.createIndexTaxonomyWriterPair(dirs); FacetIndexingParams iParams = new FacetIndexingParams() { @Override public int getPartitionSize() { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCountsCache.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCountsCache.java index 5ed5a1612f1..a767a8d06dd 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCountsCache.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestTotalFacetCountsCache.java @@ -8,6 +8,7 @@ import java.util.List; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.document.Document; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.FacetTestUtils; import org.apache.lucene.facet.FacetTestUtils.IndexTaxonomyReaderPair; import org.apache.lucene.facet.FacetTestUtils.IndexTaxonomyWriterPair; @@ -32,7 +33,6 @@ import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.MockDirectoryWrapper; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.SlowRAMDirectory; import org.apache.lucene.util._TestUtil; import org.junit.Before; @@ -55,7 +55,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestTotalFacetCountsCache extends LuceneTestCase { +public class TestTotalFacetCountsCache extends FacetTestCase { static final TotalFacetCountsCache TFC = TotalFacetCountsCache.getSingleton(); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/associations/AssociationsFacetRequestTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/associations/AssociationsFacetRequestTest.java index c8eeed3b829..d35d31d00c2 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/associations/AssociationsFacetRequestTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/associations/AssociationsFacetRequestTest.java @@ -5,6 +5,7 @@ import java.util.List; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.analysis.MockTokenizer; import org.apache.lucene.document.Document; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.associations.AssociationsFacetFields; import org.apache.lucene.facet.associations.CategoryAssociationsContainer; import org.apache.lucene.facet.associations.CategoryFloatAssociation; @@ -47,7 +48,7 @@ import org.junit.Test; */ /** Test for associations */ -public class AssociationsFacetRequestTest extends LuceneTestCase { +public class AssociationsFacetRequestTest extends FacetTestCase { private static Directory dir; private static IndexReader reader; diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetRequestTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetRequestTest.java index e9db167f06e..87ade2c1798 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetRequestTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetRequestTest.java @@ -1,15 +1,14 @@ package org.apache.lucene.facet.search.params; -import org.apache.lucene.index.IndexWriter; -import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.store.Directory; -import org.junit.Test; - -import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.search.FacetResultsHandler; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.store.Directory; +import org.junit.Test; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -28,7 +27,7 @@ import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; * limitations under the License. */ -public class FacetRequestTest extends LuceneTestCase { +public class FacetRequestTest extends FacetTestCase { @Test(expected=IllegalArgumentException.class) public void testIllegalNumResults() throws Exception { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetSearchParamsTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetSearchParamsTest.java index 7d6253f3814..e75d1766ba6 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetSearchParamsTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/params/FacetSearchParamsTest.java @@ -1,6 +1,6 @@ package org.apache.lucene.facet.search.params; -import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; /* @@ -20,7 +20,7 @@ import org.junit.Test; * limitations under the License. */ -public class FacetSearchParamsTest extends LuceneTestCase { +public class FacetSearchParamsTest extends FacetTestCase { @Test public void testSearchParamsWithNullRequest() throws Exception { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/params/MultiCategoryListIteratorTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/params/MultiCategoryListIteratorTest.java index 88a1d90e897..861e607a9df 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/params/MultiCategoryListIteratorTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/params/MultiCategoryListIteratorTest.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Random; import org.apache.lucene.document.Document; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.index.params.CategoryListParams; import org.apache.lucene.facet.index.params.PerDimensionIndexingParams; @@ -22,7 +23,6 @@ import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IntsRef; -import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.encoding.IntDecoder; import org.junit.Test; @@ -43,7 +43,7 @@ import org.junit.Test; * limitations under the License. */ -public class MultiCategoryListIteratorTest extends LuceneTestCase { +public class MultiCategoryListIteratorTest extends FacetTestCase { @Test public void testMultipleCategoryLists() throws Exception { @@ -58,7 +58,7 @@ public class MultiCategoryListIteratorTest extends LuceneTestCase { HashMap clps = new HashMap(); for (String dim : dimensions) { CategoryPath cp = new CategoryPath(dim); - CategoryListParams clp = new CategoryListParams("$" + dim); + CategoryListParams clp = randomCategoryListParams("$" + dim); clps.put(cp, clp); } PerDimensionIndexingParams indexingParams = new PerDimensionIndexingParams(clps); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/sampling/OversampleWithDepthTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/sampling/OversampleWithDepthTest.java index ffec1971c96..1117fcf1d73 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/sampling/OversampleWithDepthTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/sampling/OversampleWithDepthTest.java @@ -5,7 +5,9 @@ import java.util.Collections; import org.apache.lucene.analysis.core.KeywordAnalyzer; import org.apache.lucene.document.Document; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; +import org.apache.lucene.facet.index.params.FacetIndexingParams; import org.apache.lucene.facet.search.FacetsAccumulator; import org.apache.lucene.facet.search.FacetsCollector; import org.apache.lucene.facet.search.StandardFacetsCollector; @@ -20,7 +22,6 @@ import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.TaxonomyWriter; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; -import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; @@ -28,9 +29,7 @@ import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.store.Directory; -import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -50,16 +49,18 @@ import org.junit.Test; * limitations under the License. */ -public class OversampleWithDepthTest extends LuceneTestCase { +public class OversampleWithDepthTest extends FacetTestCase { @Test public void testCountWithdepthUsingSampling() throws Exception, IOException { Directory indexDir = newDirectory(); Directory taxoDir = newDirectory(); + FacetIndexingParams fip = new FacetIndexingParams(randomCategoryListParams()); + // index 100 docs, each with one category: ["root", docnum/10, docnum] // e.g. root/8/87 - index100Docs(indexDir, taxoDir); + index100Docs(indexDir, taxoDir, fip); DirectoryReader r = DirectoryReader.open(indexDir); TaxonomyReader tr = new DirectoryTaxonomyReader(taxoDir); @@ -69,7 +70,7 @@ public class OversampleWithDepthTest extends LuceneTestCase { facetRequest.setDepth(2); facetRequest.setResultMode(ResultMode.PER_NODE_IN_TREE); - FacetSearchParams fsp = new FacetSearchParams(facetRequest); + FacetSearchParams fsp = new FacetSearchParams(fip, facetRequest); // Craft sampling params to enforce sampling final SamplingParams params = new SamplingParams(); @@ -93,13 +94,12 @@ public class OversampleWithDepthTest extends LuceneTestCase { IOUtils.close(r, tr, indexDir, taxoDir); } - private void index100Docs(Directory indexDir, Directory taxoDir) - throws CorruptIndexException, LockObtainFailedException, IOException { + private void index100Docs(Directory indexDir, Directory taxoDir, FacetIndexingParams fip) throws IOException { IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, new KeywordAnalyzer()); IndexWriter w = new IndexWriter(indexDir, iwc); TaxonomyWriter tw = new DirectoryTaxonomyWriter(taxoDir); - FacetFields facetFields = new FacetFields(tw); + FacetFields facetFields = new FacetFields(tw, fip); for (int i = 0; i < 100; i++) { Document doc = new Document(); CategoryPath cp = new CategoryPath("root",Integer.toString(i / 10), Integer.toString(i)); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestCategoryPath.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestCategoryPath.java index ce3f29f7634..b690a643489 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestCategoryPath.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestCategoryPath.java @@ -1,6 +1,6 @@ package org.apache.lucene.facet.taxonomy; -import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; /* @@ -20,7 +20,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestCategoryPath extends LuceneTestCase { +public class TestCategoryPath extends FacetTestCase { @Test public void testBasic() { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyCombined.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyCombined.java index 2225f96ade3..cddee220b51 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyCombined.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyCombined.java @@ -7,13 +7,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; import org.apache.lucene.facet.taxonomy.directory.ParallelTaxonomyArrays; import org.apache.lucene.store.Directory; import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.store.RAMDirectory; -import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.LuceneTestCase.SuppressCodecs; import org.apache.lucene.util.SlowRAMDirectory; import org.junit.Test; @@ -37,7 +37,7 @@ import org.junit.Test; // TODO: remove this suppress if we fix the TaxoWriter Codec to a non-default (see todo in DirTW) @SuppressCodecs("SimpleText") -public class TestTaxonomyCombined extends LuceneTestCase { +public class TestTaxonomyCombined extends FacetTestCase { /** The following categories will be added to the taxonomy by fillTaxonomy(), and tested by all tests below: diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestAddTaxonomy.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestAddTaxonomy.java index 3086d1aece6..925e857ec79 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestAddTaxonomy.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestAddTaxonomy.java @@ -5,13 +5,13 @@ import java.util.HashSet; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter.DiskOrdinalMap; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter.MemoryOrdinalMap; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter.OrdinalMap; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util._TestUtil; /* @@ -31,7 +31,7 @@ import org.apache.lucene.util._TestUtil; * limitations under the License. */ -public class TestAddTaxonomy extends LuceneTestCase { +public class TestAddTaxonomy extends FacetTestCase { private void dotest(int ncats, final int range) throws Exception { final AtomicInteger numCats = new AtomicInteger(ncats); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestConcurrentFacetedIndexing.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestConcurrentFacetedIndexing.java index e120c18a435..a5b76581de3 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestConcurrentFacetedIndexing.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestConcurrentFacetedIndexing.java @@ -8,6 +8,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.lucene.document.Document; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.writercache.TaxonomyWriterCache; @@ -17,7 +18,6 @@ import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -37,7 +37,7 @@ import org.apache.lucene.util.LuceneTestCase; */ /** Tests concurrent indexing with facets. */ -public class TestConcurrentFacetedIndexing extends LuceneTestCase { +public class TestConcurrentFacetedIndexing extends FacetTestCase { // A No-Op TaxonomyWriterCache which always discards all given categories, and // always returns true in put(), to indicate some cache entries were cleared. diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyReader.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyReader.java index 301ef294310..2ae22a13c1c 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyReader.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyReader.java @@ -4,19 +4,19 @@ import java.io.IOException; import java.util.Random; import org.apache.lucene.analysis.core.KeywordAnalyzer; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.TaxonomyWriter; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.index.LogByteSizeMergePolicy; import org.apache.lucene.index.IndexWriterConfig.OpenMode; +import org.apache.lucene.index.LogByteSizeMergePolicy; import org.apache.lucene.index.LogMergePolicy; import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -36,7 +36,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestDirectoryTaxonomyReader extends LuceneTestCase { +public class TestDirectoryTaxonomyReader extends FacetTestCase { @Test public void testCloseAfterIncRef() throws Exception { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyWriter.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyWriter.java index bed2e60f948..8c10ffba7c0 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyWriter.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyWriter.java @@ -7,6 +7,7 @@ import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter.MemoryOrdinalMap; @@ -21,7 +22,6 @@ import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.index.SegmentInfos; import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.Directory; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -41,7 +41,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestDirectoryTaxonomyWriter extends LuceneTestCase { +public class TestDirectoryTaxonomyWriter extends FacetTestCase { // A No-Op TaxonomyWriterCache which always discards all given categories, and // always returns true in put(), to indicate some cache entries were cleared. diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCharBlockArray.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCharBlockArray.java index b6ff0219b82..747acfe1df3 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCharBlockArray.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCharBlockArray.java @@ -9,11 +9,9 @@ import java.nio.ByteBuffer; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; -import org.junit.Test; - +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.facet.taxonomy.writercache.cl2o.CharBlockArray; +import org.junit.Test; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -32,7 +30,7 @@ import org.apache.lucene.facet.taxonomy.writercache.cl2o.CharBlockArray; * limitations under the License. */ -public class TestCharBlockArray extends LuceneTestCase { +public class TestCharBlockArray extends FacetTestCase { @Test public void testArray() throws Exception { CharBlockArray array = new CharBlockArray(); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCompactLabelToOrdinal.java b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCompactLabelToOrdinal.java index 74e1743a1d1..f9fab9708f4 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCompactLabelToOrdinal.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/taxonomy/writercache/cl2o/TestCompactLabelToOrdinal.java @@ -8,14 +8,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Random; -import org.junit.Test; - -import org.apache.lucene.util.IOUtils; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util._TestUtil; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.taxonomy.CategoryPath; -import org.apache.lucene.facet.taxonomy.writercache.cl2o.CompactLabelToOrdinal; -import org.apache.lucene.facet.taxonomy.writercache.cl2o.LabelToOrdinal; +import org.apache.lucene.util.IOUtils; +import org.apache.lucene.util._TestUtil; +import org.junit.Test; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -34,7 +31,7 @@ import org.apache.lucene.facet.taxonomy.writercache.cl2o.LabelToOrdinal; * limitations under the License. */ -public class TestCompactLabelToOrdinal extends LuceneTestCase { +public class TestCompactLabelToOrdinal extends FacetTestCase { @Test public void testL2O() throws Exception { diff --git a/lucene/facet/src/test/org/apache/lucene/facet/util/TestScoredDocIDsUtils.java b/lucene/facet/src/test/org/apache/lucene/facet/util/TestScoredDocIDsUtils.java index 3ae661521d9..cb1d7eac9fc 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/util/TestScoredDocIDsUtils.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/util/TestScoredDocIDsUtils.java @@ -9,6 +9,7 @@ import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; import org.apache.lucene.document.StringField; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.search.ScoredDocIDs; import org.apache.lucene.facet.search.ScoredDocIDsIterator; import org.apache.lucene.facet.search.ScoredDocIdCollector; @@ -25,7 +26,6 @@ import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.Bits; import org.apache.lucene.util.FixedBitSet; -import org.apache.lucene.util.LuceneTestCase; import org.junit.Test; /* @@ -45,7 +45,7 @@ import org.junit.Test; * limitations under the License. */ -public class TestScoredDocIDsUtils extends LuceneTestCase { +public class TestScoredDocIDsUtils extends FacetTestCase { @Test public void testComplementIterator() throws Exception { diff --git a/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayInputStreamTest.java b/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayInputStreamTest.java index 2c5dc76490b..e88bd22895f 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayInputStreamTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayInputStreamTest.java @@ -3,11 +3,9 @@ package org.apache.lucene.util; import java.io.IOException; import java.util.Arrays; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.UnsafeByteArrayInputStream; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -25,7 +23,7 @@ import org.apache.lucene.util.UnsafeByteArrayInputStream; * limitations under the License. */ -public class UnsafeByteArrayInputStreamTest extends LuceneTestCase { +public class UnsafeByteArrayInputStreamTest extends FacetTestCase { @Test public void testSimple() throws IOException { diff --git a/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayOutputStreamTest.java b/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayOutputStreamTest.java index b3bad7987b5..b7af76aa2f6 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayOutputStreamTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/UnsafeByteArrayOutputStreamTest.java @@ -2,11 +2,9 @@ package org.apache.lucene.util; import java.io.IOException; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.UnsafeByteArrayOutputStream; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -24,7 +22,7 @@ import org.apache.lucene.util.UnsafeByteArrayOutputStream; * limitations under the License. */ -public class UnsafeByteArrayOutputStreamTest extends LuceneTestCase { +public class UnsafeByteArrayOutputStreamTest extends FacetTestCase { @Test public void testSimpleWrite() throws IOException { diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/ArrayHashMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/ArrayHashMapTest.java index 2a79064fc09..4b0c3ba90fe 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/ArrayHashMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/ArrayHashMapTest.java @@ -4,11 +4,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Random; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.ArrayHashMap; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -26,7 +24,7 @@ import org.apache.lucene.util.collections.ArrayHashMap; * limitations under the License. */ -public class ArrayHashMapTest extends LuceneTestCase { +public class ArrayHashMapTest extends FacetTestCase { public static final int RANDOM_TEST_NUM_ITERATIONS = 100; // set to 100,000 for deeper test diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/FloatToObjectMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/FloatToObjectMapTest.java index 5b4d1a3443a..a627ea1e423 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/FloatToObjectMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/FloatToObjectMapTest.java @@ -1,13 +1,11 @@ package org.apache.lucene.util.collections; -import org.junit.Test; import java.util.HashSet; import java.util.Iterator; import java.util.Random; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.FloatIterator; -import org.apache.lucene.util.collections.FloatToObjectMap; +import org.apache.lucene.facet.FacetTestCase; +import org.junit.Test; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -26,7 +24,7 @@ import org.apache.lucene.util.collections.FloatToObjectMap; * limitations under the License. */ -public class FloatToObjectMapTest extends LuceneTestCase { +public class FloatToObjectMapTest extends FacetTestCase { @Test public void test0() { diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/IntArrayTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/IntArrayTest.java index 87843c180ef..73b33465622 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/IntArrayTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/IntArrayTest.java @@ -1,10 +1,8 @@ package org.apache.lucene.util.collections; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.IntArray; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -22,7 +20,7 @@ import org.apache.lucene.util.collections.IntArray; * limitations under the License. */ -public class IntArrayTest extends LuceneTestCase { +public class IntArrayTest extends FacetTestCase { @Test public void test0() { diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/IntHashSetTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/IntHashSetTest.java index 94692f293f2..1440925f23a 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/IntHashSetTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/IntHashSetTest.java @@ -2,11 +2,9 @@ package org.apache.lucene.util.collections; import java.util.HashSet; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.IntHashSet; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -24,7 +22,7 @@ import org.apache.lucene.util.collections.IntHashSet; * limitations under the License. */ -public class IntHashSetTest extends LuceneTestCase { +public class IntHashSetTest extends FacetTestCase { @Test public void test0() { diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToDoubleMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToDoubleMapTest.java index 46d9d1645e2..9380cc1e444 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToDoubleMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToDoubleMapTest.java @@ -1,15 +1,11 @@ package org.apache.lucene.util.collections; -import org.junit.Test; - -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.DoubleIterator; -import org.apache.lucene.util.collections.IntIterator; -import org.apache.lucene.util.collections.IntToDoubleMap; - import java.util.HashSet; import java.util.Random; +import org.apache.lucene.facet.FacetTestCase; +import org.junit.Test; + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -27,7 +23,8 @@ import java.util.Random; * limitations under the License. */ -public class IntToDoubleMapTest extends LuceneTestCase { +public class IntToDoubleMapTest extends FacetTestCase { + private static void assertGround(double value) { assertEquals(IntToDoubleMap.GROUND, value, Double.MAX_VALUE); } diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToFloatMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToFloatMapTest.java index 034e31d17d6..4c9fc88b635 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToFloatMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToFloatMapTest.java @@ -1,15 +1,11 @@ package org.apache.lucene.util.collections; -import org.junit.Test; - -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.FloatIterator; -import org.apache.lucene.util.collections.IntIterator; -import org.apache.lucene.util.collections.IntToFloatMap; - import java.util.HashSet; import java.util.Random; +import org.apache.lucene.facet.FacetTestCase; +import org.junit.Test; + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -27,7 +23,8 @@ import java.util.Random; * limitations under the License. */ -public class IntToFloatMapTest extends LuceneTestCase { +public class IntToFloatMapTest extends FacetTestCase { + private static void assertGround(float value) { assertEquals(IntToFloatMap.GROUND, value, Float.MAX_VALUE); } diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToIntMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToIntMapTest.java index dbd40d21e70..6877a89997b 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToIntMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToIntMapTest.java @@ -1,12 +1,10 @@ package org.apache.lucene.util.collections; -import org.junit.Test; import java.util.HashSet; import java.util.Random; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.IntIterator; -import org.apache.lucene.util.collections.IntToIntMap; +import org.apache.lucene.facet.FacetTestCase; +import org.junit.Test; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -25,7 +23,7 @@ import org.apache.lucene.util.collections.IntToIntMap; * limitations under the License. */ -public class IntToIntMapTest extends LuceneTestCase { +public class IntToIntMapTest extends FacetTestCase { private static void assertGround(int value) { assertEquals(IntToIntMap.GROUD, value); diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToObjectMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToObjectMapTest.java index 7cb8b8b02fa..91337451b68 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/IntToObjectMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/IntToObjectMapTest.java @@ -4,12 +4,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Random; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.IntIterator; -import org.apache.lucene.util.collections.IntToObjectMap; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -27,7 +24,7 @@ import org.apache.lucene.util.collections.IntToObjectMap; * limitations under the License. */ -public class IntToObjectMapTest extends LuceneTestCase { +public class IntToObjectMapTest extends FacetTestCase { @Test public void test0() { diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToFloatMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToFloatMapTest.java index d1c4f27b970..7d00a16b39a 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToFloatMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToFloatMapTest.java @@ -1,15 +1,12 @@ package org.apache.lucene.util.collections; -import org.junit.Test; - -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.FloatIterator; -import org.apache.lucene.util.collections.ObjectToFloatMap; - import java.util.HashSet; import java.util.Iterator; import java.util.Random; +import org.apache.lucene.facet.FacetTestCase; +import org.junit.Test; + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -27,7 +24,7 @@ import java.util.Random; * limitations under the License. */ -public class ObjectToFloatMapTest extends LuceneTestCase { +public class ObjectToFloatMapTest extends FacetTestCase { @Test public void test0() { diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToIntMapTest.java b/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToIntMapTest.java index a5629a3dfe3..94d74ffd7a0 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToIntMapTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/ObjectToIntMapTest.java @@ -6,6 +6,7 @@ import java.util.Random; import org.junit.Test; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.collections.IntIterator; import org.apache.lucene.util.collections.ObjectToIntMap; @@ -27,7 +28,7 @@ import org.apache.lucene.util.collections.ObjectToIntMap; * limitations under the License. */ -public class ObjectToIntMapTest extends LuceneTestCase { +public class ObjectToIntMapTest extends FacetTestCase { @Test public void test0() { diff --git a/lucene/facet/src/test/org/apache/lucene/util/collections/TestLRUHashMap.java b/lucene/facet/src/test/org/apache/lucene/util/collections/TestLRUHashMap.java index 01f028b8876..ddf3301eeda 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/collections/TestLRUHashMap.java +++ b/lucene/facet/src/test/org/apache/lucene/util/collections/TestLRUHashMap.java @@ -1,10 +1,8 @@ package org.apache.lucene.util.collections; +import org.apache.lucene.facet.FacetTestCase; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.collections.LRUHashMap; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -22,7 +20,7 @@ import org.apache.lucene.util.collections.LRUHashMap; * limitations under the License. */ -public class TestLRUHashMap extends LuceneTestCase { +public class TestLRUHashMap extends FacetTestCase { // testLRU() tests that the specified size limit is indeed honored, and // the remaining objects in the map are indeed those that have been most // recently used diff --git a/lucene/facet/src/test/org/apache/lucene/util/encoding/EncodingTest.java b/lucene/facet/src/test/org/apache/lucene/util/encoding/EncodingTest.java index 411b9aec79e..6383beaec0b 100644 --- a/lucene/facet/src/test/org/apache/lucene/util/encoding/EncodingTest.java +++ b/lucene/facet/src/test/org/apache/lucene/util/encoding/EncodingTest.java @@ -3,9 +3,9 @@ package org.apache.lucene.util.encoding; import java.io.IOException; import java.util.Arrays; +import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.IntsRef; -import org.apache.lucene.util.LuceneTestCase; import org.junit.BeforeClass; import org.junit.Test; @@ -26,7 +26,7 @@ import org.junit.Test; * limitations under the License. */ -public class EncodingTest extends LuceneTestCase { +public class EncodingTest extends FacetTestCase { private static IntsRef uniqueSortedData, data; diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 03f39709944..9ea9b2fd4da 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -63,6 +63,9 @@ Detailed Change List New Features ---------------------- +* SOLR-4043: Add ability to get success/failure responses from Collections API. + (Raintung Li, Mark Miller) + Bug Fixes ---------------------- @@ -80,6 +83,16 @@ Bug Fixes * SOLR-4349 : Admin UI - Query Interface does not work in IE (steffkes) +* SOLR-4359: The RecentUpdates#update method should treat a problem reading the + next record the same as a problem parsing the record - log the exception and + break. (Mark Miller) + +* SOLR-4225: Term info page under schema browser shows incorrect count of terms + (steffkes) + +* SOLR-3926: Solr should support better way of finding active sorts (Eirik Lygre via + Erick Erickson) + Optimizations ---------------------- @@ -100,6 +113,8 @@ Other Changes * SOLR-4348: Make the lock type configurable by system property by default. (Mark Miller) +* SOLR-4353: Renamed example jetty context file to reduce confusion (hossman) + ================== 4.1.0 ================== Versions of Major Components @@ -607,6 +622,9 @@ Bug Fixes * SOLR-4266: HttpSolrServer does not release connection properly on exception when no response parser is used. (Steve Molloy via Mark Miller) +* SOLR-2298: Updated JavaDoc for SolrDocument.addField and SolrInputDocument.addField + to have more information on name and value parameters. (Siva Natarajan) + Other Changes ---------------------- diff --git a/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestBuiltInEvaluators.java b/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestBuiltInEvaluators.java index 3c669d90e8e..f07d4abacab 100644 --- a/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestBuiltInEvaluators.java +++ b/solr/contrib/dataimporthandler/src/test/org/apache/solr/handler/dataimport/TestBuiltInEvaluators.java @@ -17,7 +17,6 @@ package org.apache.solr.handler.dataimport; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import java.net.URLEncoder; @@ -109,58 +108,63 @@ public class TestBuiltInEvaluators extends AbstractDataImportHandlerTestCase { } - private Date getNow() { - Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"), - Locale.ROOT); + private Date twoDaysAgo(Locale l, TimeZone tz) { + Calendar calendar = Calendar.getInstance(tz, l); calendar.add(Calendar.DAY_OF_YEAR, -2); return calendar.getTime(); } @Test - @Ignore("fails if somewhere on earth is a DST change") public void testDateFormatEvaluator() { Evaluator dateFormatEval = new DateFormatEvaluator(); ContextImpl context = new ContextImpl(null, resolver, null, Context.FULL_DUMP, Collections. emptyMap(), null, null); - String currentLocale = Locale.getDefault().toString(); + + Locale rootLocale = Locale.ROOT; + Locale defaultLocale = Locale.getDefault(); + TimeZone defaultTz = TimeZone.getDefault(); + { - { - SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH", Locale.ROOT); - String sdf = sdfDate.format(getNow()); - String dfe = dateFormatEval.evaluate("'NOW-2DAYS','yyyy-MM-dd HH'", context); - assertEquals(sdf,dfe); - } - { - SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH", Locale.getDefault()); - String sdf = sdfDate.format(getNow()); - String dfe = dateFormatEval.evaluate("'NOW-2DAYS','yyyy-MM-dd HH','"+ currentLocale + "'", context); - assertEquals(sdf,dfe); - for(String tz : TimeZone.getAvailableIDs()) { - sdfDate.setTimeZone(TimeZone.getTimeZone(tz)); - sdf = sdfDate.format(getNow()); - dfe = dateFormatEval.evaluate("'NOW-2DAYS','yyyy-MM-dd HH','" + currentLocale + "','" + tz + "'", context); - assertEquals(sdf,dfe); - } + SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH", rootLocale); + String sdf = sdfDate.format(twoDaysAgo(rootLocale, defaultTz)); + String dfe = dateFormatEval.evaluate("'NOW-2DAYS','yyyy-MM-dd HH'", context); + assertEquals(sdf,dfe); + } + { + SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH", defaultLocale); + String sdf = sdfDate.format(twoDaysAgo(defaultLocale, TimeZone.getDefault())); + String dfe = dateFormatEval.evaluate( + "'NOW-2DAYS','yyyy-MM-dd HH','" + defaultLocale + "'", context); + assertEquals(sdf,dfe); + for(String tzStr : TimeZone.getAvailableIDs()) { + TimeZone tz = TimeZone.getTimeZone(tzStr); + sdfDate.setTimeZone(tz); + sdf = sdfDate.format(twoDaysAgo(defaultLocale, tz)); + dfe = dateFormatEval.evaluate( + "'NOW-2DAYS','yyyy-MM-dd HH','" + defaultLocale + "','" + tzStr + "'", context); + assertEquals(sdf,dfe); } } + Date d = new Date(); Map map = new HashMap(); map.put("key", d); resolver.addNamespace("A", map); - + assertEquals( - new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).format(d), + new SimpleDateFormat("yyyy-MM-dd HH:mm", rootLocale).format(d), dateFormatEval.evaluate("A.key, 'yyyy-MM-dd HH:mm'", context)); assertEquals( - new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(d), - dateFormatEval.evaluate("A.key, 'yyyy-MM-dd HH:mm','" + currentLocale - + "'", context)); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()); - for(String tz : TimeZone.getAvailableIDs()) { - sdf.setTimeZone(TimeZone.getTimeZone(tz)); + new SimpleDateFormat("yyyy-MM-dd HH:mm", defaultLocale).format(d), + dateFormatEval.evaluate("A.key, 'yyyy-MM-dd HH:mm','" + defaultLocale + "'", context)); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", defaultLocale); + for(String tzStr : TimeZone.getAvailableIDs()) { + TimeZone tz = TimeZone.getTimeZone(tzStr); + sdf.setTimeZone(tz); assertEquals( sdf.format(d), - dateFormatEval.evaluate("A.key, 'yyyy-MM-dd HH:mm','" + currentLocale + "', '" + tz + "'", context)); + dateFormatEval.evaluate( + "A.key, 'yyyy-MM-dd HH:mm','" + defaultLocale + "', '" + tzStr + "'", context)); } diff --git a/solr/core/src/java/org/apache/solr/cloud/DistributedQueue.java b/solr/core/src/java/org/apache/solr/cloud/DistributedQueue.java index 61fd31ddb6f..30f8008afad 100644 --- a/solr/core/src/java/org/apache/solr/cloud/DistributedQueue.java +++ b/solr/core/src/java/org/apache/solr/cloud/DistributedQueue.java @@ -48,6 +48,8 @@ public class DistributedQueue { private final String prefix = "qn-"; + private final String response_prefix = "qnr-" ; + public DistributedQueue(SolrZkClient zookeeper, String dir, List acl) { this.dir = dir; @@ -100,7 +102,7 @@ public class DistributedQueue { * * @return the data at the head of the queue. */ - public byte[] element() throws NoSuchElementException, KeeperException, + private QueueEvent element() throws NoSuchElementException, KeeperException, InterruptedException { TreeMap orderedChildren; @@ -122,7 +124,7 @@ public class DistributedQueue { for (String headNode : orderedChildren.values()) { if (headNode != null) { try { - return zookeeper.getData(dir + "/" + headNode, null, null, true); + return new QueueEvent(dir + "/" + headNode, zookeeper.getData(dir + "/" + headNode, null, null, true), null); } catch (KeeperException.NoNodeException e) { // Another client removed the node first, try next } @@ -162,17 +164,41 @@ public class DistributedQueue { } } + /** + * Remove the event and save the response into the other path. + * + */ + public byte[] remove(QueueEvent event) throws KeeperException, + InterruptedException { + String path = event.getId(); + String responsePath = dir + "/" + response_prefix + + path.substring(path.lastIndexOf("-") + 1); + if (zookeeper.exists(responsePath, true)) { + zookeeper.setData(responsePath, event.getBytes(), true); + } + byte[] data = zookeeper.getData(path, null, null, true); + zookeeper.delete(path, -1, true); + return data; + } + + private class LatchChildWatcher implements Watcher { Object lock = new Object(); + private WatchedEvent event = null; public LatchChildWatcher() {} + public LatchChildWatcher(Object lock) { + this.lock = lock; + } + @Override public void process(WatchedEvent event) { LOG.info("Watcher fired on path: " + event.getPath() + " state: " + event.getState() + " type " + event.getType()); synchronized (lock) { + this.event = event; lock.notifyAll(); } } @@ -182,6 +208,10 @@ public class DistributedQueue { lock.wait(timeout); } } + + public WatchedEvent getWatchedEvent() { + return event; + } } /** @@ -225,22 +255,51 @@ public class DistributedQueue { */ public boolean offer(byte[] data) throws KeeperException, InterruptedException { + return createData(dir + "/" + prefix, data, + CreateMode.PERSISTENT_SEQUENTIAL) != null; + } + + /** + * Inserts data into zookeeper. + * + * @return true if data was successfully added + */ + private String createData(String path, byte[] data, CreateMode mode) + throws KeeperException, InterruptedException { for (;;) { try { - zookeeper.create(dir + "/" + prefix, data, acl, - CreateMode.PERSISTENT_SEQUENTIAL, true); - return true; + return zookeeper.create(path, data, acl, mode, true); } catch (KeeperException.NoNodeException e) { try { zookeeper.create(dir, new byte[0], acl, CreateMode.PERSISTENT, true); } catch (KeeperException.NodeExistsException ne) { - //someone created it + // someone created it } } } - - - + } + + /** + * Offer the data and wait for the response + * + */ + public QueueEvent offer(byte[] data, long timeout) throws KeeperException, + InterruptedException { + String path = createData(dir + "/" + prefix, data, + CreateMode.PERSISTENT_SEQUENTIAL); + String watchID = createData( + dir + "/" + response_prefix + path.substring(path.lastIndexOf("-") + 1), + null, CreateMode.EPHEMERAL); + Object lock = new Object(); + LatchChildWatcher watcher = new LatchChildWatcher(lock); + synchronized (lock) { + if (zookeeper.exists(watchID, watcher, true) != null) { + watcher.await(timeout); + } + } + byte[] bytes = zookeeper.getData(watchID, null, null, true); + zookeeper.delete(watchID, -1, true); + return new QueueEvent(watchID, bytes, watcher.getWatchedEvent()); } /** @@ -251,21 +310,74 @@ public class DistributedQueue { */ public byte[] peek() throws KeeperException, InterruptedException { try { - return element(); + return element().getBytes(); } catch (NoSuchElementException e) { return null; } } + public static class QueueEvent { + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + QueueEvent other = (QueueEvent) obj; + if (id == null) { + if (other.id != null) return false; + } else if (!id.equals(other.id)) return false; + return true; + } + + private WatchedEvent event = null; + private String id; + private byte[] bytes; + + QueueEvent(String id, byte[] bytes, WatchedEvent event) { + this.id = id; + this.bytes = bytes; + this.event = event; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setBytes(byte[] bytes) { + this.bytes = bytes; + } + + public byte[] getBytes() { + return bytes; + } + + public WatchedEvent getWatchedEvent() { + return event; + } + + } + /** * Returns the data at the first element of the queue, or null if the queue is * empty. * * @return data at the first element of the queue, or null. */ - public byte[] peek(boolean block) throws KeeperException, InterruptedException { + public QueueEvent peek(boolean block) throws KeeperException, InterruptedException { if (!block) { - return peek(); + return element(); } TreeMap orderedChildren; @@ -286,7 +398,7 @@ public class DistributedQueue { String path = dir + "/" + headNode; try { byte[] data = zookeeper.getData(path, null, null, true); - return data; + return new QueueEvent(path, data, childWatcher.getWatchedEvent()); } catch (KeeperException.NoNodeException e) { // Another client deleted the node first. } diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerCollectionProcessor.java b/solr/core/src/java/org/apache/solr/cloud/OverseerCollectionProcessor.java index cbde272695f..ae09ff48157 100644 --- a/solr/core/src/java/org/apache/solr/cloud/OverseerCollectionProcessor.java +++ b/solr/core/src/java/org/apache/solr/cloud/OverseerCollectionProcessor.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.solr.client.solrj.SolrResponse; +import org.apache.solr.cloud.DistributedQueue.QueueEvent; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.cloud.ClosableThread; @@ -36,6 +38,7 @@ import org.apache.solr.common.cloud.ZooKeeperException; import org.apache.solr.common.params.CoreAdminParams; import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction; import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.StrUtils; import org.apache.solr.handler.component.ShardHandler; import org.apache.solr.handler.component.ShardRequest; @@ -94,47 +97,33 @@ public class OverseerCollectionProcessor implements Runnable, ClosableThread { @Override public void run() { - log.info("Process current queue of collection messages"); - while (amILeader() && !isClosed) { - try { - byte[] head = workQueue.peek(true); - - //if (head != null) { // should not happen since we block above - final ZkNodeProps message = ZkNodeProps.load(head); - final String operation = message.getStr(QUEUE_OPERATION); - try { - boolean success = processMessage(message, operation); - if (!success) { - // TODO: what to do on failure / partial failure - // if we fail, do we clean up then ? - SolrException.log(log, - "Collection " + operation + " of " + message.getStr("name") - + " failed"); - } - } catch(Throwable t) { - SolrException.log(log, - "Collection " + operation + " of " + message.getStr("name") - + " failed", t); - } - //} - - - workQueue.poll(); - - } catch (KeeperException e) { - if (e.code() == KeeperException.Code.SESSIONEXPIRED - || e.code() == KeeperException.Code.CONNECTIONLOSS) { - log.warn("Overseer cannot talk to ZK"); - return; - } - SolrException.log(log, "", e); - throw new ZooKeeperException(SolrException.ErrorCode.SERVER_ERROR, "", - e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } - } + log.info("Process current queue of collection creations"); + while (amILeader() && !isClosed) { + try { + QueueEvent head = workQueue.peek(true); + final ZkNodeProps message = ZkNodeProps.load(head.getBytes()); + log.info("Overseer Collection Processor: Get the message id:" + head.getId() + " message:" + message.toString()); + final String operation = message.getStr(QUEUE_OPERATION); + SolrResponse response = processMessage(message, operation); + head.setBytes(SolrResponse.serializable(response)); + workQueue.remove(head); + log.info("Overseer Collection Processor: Message id:" + head.getId() + " complete, response:"+ response.getResponse().toString()); + } catch (KeeperException e) { + if (e.code() == KeeperException.Code.SESSIONEXPIRED + || e.code() == KeeperException.Code.CONNECTIONLOSS) { + log.warn("Overseer cannot talk to ZK"); + return; + } + SolrException.log(log, "", e); + throw new ZooKeeperException( + SolrException.ErrorCode.SERVER_ERROR, "", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } catch (Throwable e) { + SolrException.log(log, "", e); + } + } } public void close() { @@ -157,21 +146,49 @@ public class OverseerCollectionProcessor implements Runnable, ClosableThread { return false; } - protected boolean processMessage(ZkNodeProps message, String operation) { - if (CREATECOLLECTION.equals(operation)) { - return createCollection(zkStateReader.getClusterState(), message); - } else if (DELETECOLLECTION.equals(operation)) { - ModifiableSolrParams params = new ModifiableSolrParams(); - params.set(CoreAdminParams.ACTION, CoreAdminAction.UNLOAD.toString()); - params.set(CoreAdminParams.DELETE_INSTANCE_DIR, true); - return collectionCmd(zkStateReader.getClusterState(), message, params); - } else if (RELOADCOLLECTION.equals(operation)) { - ModifiableSolrParams params = new ModifiableSolrParams(); - params.set(CoreAdminParams.ACTION, CoreAdminAction.RELOAD.toString()); - return collectionCmd(zkStateReader.getClusterState(), message, params); + + protected SolrResponse processMessage(ZkNodeProps message, String operation) { + + NamedList results = new NamedList(); + try { + if (CREATECOLLECTION.equals(operation)) { + createCollection(zkStateReader.getClusterState(), message); + } else if (DELETECOLLECTION.equals(operation)) { + ModifiableSolrParams params = new ModifiableSolrParams(); + params.set(CoreAdminParams.ACTION, CoreAdminAction.UNLOAD.toString()); + params.set(CoreAdminParams.DELETE_INSTANCE_DIR, true); + collectionCmd(zkStateReader.getClusterState(), message, params); + } else if (RELOADCOLLECTION.equals(operation)) { + ModifiableSolrParams params = new ModifiableSolrParams(); + params.set(CoreAdminParams.ACTION, CoreAdminAction.RELOAD.toString()); + collectionCmd(zkStateReader.getClusterState(), message, params); + } else { + throw new SolrException(ErrorCode.BAD_REQUEST, "Unknow the operation:" + + operation); + } + int failed = 0; + ShardResponse srsp; + + do { + srsp = shardHandler.takeCompletedIncludingErrors(); + if (srsp != null) { + Throwable e = srsp.getException(); + if (e != null) { + failed++; + log.error("Error talking to shard: " + srsp.getShard(), e); + results.add(srsp.getShard(), e); + } else { + results.add(srsp.getShard(), srsp.getSolrResponse().getResponse()); + } + } + } while (srsp != null); + } catch (SolrException ex) { + SolrException.log(log, "Collection " + operation + " of " + operation + + " failed"); + results.add("Operation " + operation + " cause exception:", ex); + } finally { + return new OverseerSolrResponse(results); } - // unknown command, toss it from our queue - return true; } private boolean createCollection(ClusterState clusterState, ZkNodeProps message) { diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerSolrResponse.java b/solr/core/src/java/org/apache/solr/cloud/OverseerSolrResponse.java new file mode 100644 index 00000000000..9dfc0773c02 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cloud/OverseerSolrResponse.java @@ -0,0 +1,47 @@ +package org.apache.solr.cloud; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +import org.apache.solr.client.solrj.SolrResponse; +import org.apache.solr.common.util.NamedList; + +public class OverseerSolrResponse extends SolrResponse { + + NamedList responseList = null; + + public OverseerSolrResponse(NamedList list) { + responseList = list; + } + + @Override + public long getElapsedTime() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setResponse(NamedList rsp) { + this.responseList = rsp; + } + + @Override + public NamedList getResponse() { + return responseList; + } + +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java index 1919c090775..2060dfb212f 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java @@ -21,10 +21,12 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import org.apache.solr.client.solrj.SolrResponse; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.apache.solr.client.solrj.request.CoreAdminRequest; import org.apache.solr.client.solrj.request.CoreAdminRequest.RequestSyncShard; +import org.apache.solr.cloud.DistributedQueue.QueueEvent; import org.apache.solr.cloud.Overseer; import org.apache.solr.cloud.OverseerCollectionProcessor; import org.apache.solr.common.SolrException; @@ -127,7 +129,35 @@ public class CollectionsHandler extends RequestHandlerBase { rsp.setHttpCaching(false); } - + + public static long DEFAULT_ZK_TIMEOUT = 60*1000; + + private void handleResponse(String operation, ZkNodeProps m, + SolrQueryResponse rsp) throws KeeperException, InterruptedException { + long time = System.currentTimeMillis(); + QueueEvent event = coreContainer.getZkController() + .getOverseerCollectionQueue() + .offer(ZkStateReader.toJSON(m), DEFAULT_ZK_TIMEOUT); + if (event.getBytes() != null) { + SolrResponse response = SolrResponse.deserialize(event.getBytes()); + rsp.getValues().addAll(response.getResponse()); + } else { + if (System.currentTimeMillis() - time >= DEFAULT_ZK_TIMEOUT) { + throw new SolrException(ErrorCode.SERVER_ERROR, operation + + " the collection time out:" + DEFAULT_ZK_TIMEOUT / 1000 + "s"); + } else if (event.getWatchedEvent() != null) { + throw new SolrException(ErrorCode.SERVER_ERROR, operation + + " the collection error [Watcher fired on path: " + + event.getWatchedEvent().getPath() + " state: " + + event.getWatchedEvent().getState() + " type " + + event.getWatchedEvent().getType() + "]"); + } else { + throw new SolrException(ErrorCode.SERVER_ERROR, operation + + " the collection unkown case"); + } + } + } + private void handleReloadAction(SolrQueryRequest req, SolrQueryResponse rsp) throws KeeperException, InterruptedException { log.info("Reloading Collection : " + req.getParamString()); String name = req.getParams().required().get("name"); @@ -135,8 +165,7 @@ public class CollectionsHandler extends RequestHandlerBase { ZkNodeProps m = new ZkNodeProps(Overseer.QUEUE_OPERATION, OverseerCollectionProcessor.RELOADCOLLECTION, "name", name); - // TODO: what if you want to block until the collection is available? - coreContainer.getZkController().getOverseerCollectionQueue().offer(ZkStateReader.toJSON(m)); + handleResponse(OverseerCollectionProcessor.RELOADCOLLECTION, m, rsp); } private void handleSyncShardAction(SolrQueryRequest req, SolrQueryResponse rsp) throws KeeperException, InterruptedException, SolrServerException, IOException { @@ -168,8 +197,7 @@ public class CollectionsHandler extends RequestHandlerBase { ZkNodeProps m = new ZkNodeProps(Overseer.QUEUE_OPERATION, OverseerCollectionProcessor.DELETECOLLECTION, "name", name); - // TODO: what if you want to block until the collection is available? - coreContainer.getZkController().getOverseerCollectionQueue().offer(ZkStateReader.toJSON(m)); + handleResponse(OverseerCollectionProcessor.DELETECOLLECTION, m, rsp); } @@ -208,8 +236,7 @@ public class CollectionsHandler extends RequestHandlerBase { ZkNodeProps m = new ZkNodeProps(props); - // TODO: what if you want to block until the collection is available? - coreContainer.getZkController().getOverseerCollectionQueue().offer(ZkStateReader.toJSON(m)); + handleResponse(OverseerCollectionProcessor.CREATECOLLECTION, m, rsp); } public static ModifiableSolrParams params(String... params) { diff --git a/solr/core/src/java/org/apache/solr/schema/TrieField.java b/solr/core/src/java/org/apache/solr/schema/TrieField.java index 77d29b71cfc..afe4da286b0 100644 --- a/solr/core/src/java/org/apache/solr/schema/TrieField.java +++ b/solr/core/src/java/org/apache/solr/schema/TrieField.java @@ -29,8 +29,6 @@ import org.apache.lucene.document.FieldType.NumericType; import org.apache.lucene.document.FloatField; import org.apache.lucene.document.IntField; import org.apache.lucene.document.LongField; -import org.apache.lucene.index.GeneralField; -import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.StorableField; import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.queries.function.valuesource.DoubleFieldSource; @@ -48,7 +46,6 @@ import org.apache.solr.analysis.TrieTokenizerFactory; import org.apache.solr.common.SolrException; import org.apache.solr.response.TextResponseWriter; import org.apache.solr.search.QParser; -import org.apache.solr.search.function.*; /** * Provides field types to support for Lucene's {@link @@ -311,19 +308,19 @@ public class TrieField extends PrimitiveFieldType { String s = val.toString(); switch (type) { case INTEGER: - NumericUtils.intToPrefixCoded(Integer.parseInt(s), 0, result); + NumericUtils.intToPrefixCodedBytes(Integer.parseInt(s), 0, result); break; case FLOAT: - NumericUtils.intToPrefixCoded(NumericUtils.floatToSortableInt(Float.parseFloat(s)), 0, result); + NumericUtils.intToPrefixCodedBytes(NumericUtils.floatToSortableInt(Float.parseFloat(s)), 0, result); break; case LONG: - NumericUtils.longToPrefixCoded(Long.parseLong(s), 0, result); + NumericUtils.longToPrefixCodedBytes(Long.parseLong(s), 0, result); break; case DOUBLE: - NumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(Double.parseDouble(s)), 0, result); + NumericUtils.longToPrefixCodedBytes(NumericUtils.doubleToSortableLong(Double.parseDouble(s)), 0, result); break; case DATE: - NumericUtils.longToPrefixCoded(dateField.parseMath(null, s).getTime(), 0, result); + NumericUtils.longToPrefixCodedBytes(dateField.parseMath(null, s).getTime(), 0, result); break; default: throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + type); @@ -419,17 +416,17 @@ public class TrieField extends PrimitiveFieldType { if (val != null) { switch (type) { case INTEGER: - NumericUtils.intToPrefixCoded(val.intValue(), 0, bytes); + NumericUtils.intToPrefixCodedBytes(val.intValue(), 0, bytes); break; case FLOAT: - NumericUtils.intToPrefixCoded(NumericUtils.floatToSortableInt(val.floatValue()), 0, bytes); + NumericUtils.intToPrefixCodedBytes(NumericUtils.floatToSortableInt(val.floatValue()), 0, bytes); break; case LONG: //fallthrough! case DATE: - NumericUtils.longToPrefixCoded(val.longValue(), 0, bytes); + NumericUtils.longToPrefixCodedBytes(val.longValue(), 0, bytes); break; case DOUBLE: - NumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(val.doubleValue()), 0, bytes); + NumericUtils.longToPrefixCodedBytes(NumericUtils.doubleToSortableLong(val.doubleValue()), 0, bytes); break; default: throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + f.name()); @@ -441,7 +438,7 @@ public class TrieField extends PrimitiveFieldType { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Invalid field contents: "+f.name()); switch (type) { case INTEGER: - NumericUtils.intToPrefixCoded(toInt(bytesRef.bytes, bytesRef.offset), 0, bytes); + NumericUtils.intToPrefixCodedBytes(toInt(bytesRef.bytes, bytesRef.offset), 0, bytes); break; case FLOAT: { // WARNING: Code Duplication! Keep in sync with o.a.l.util.NumericUtils! @@ -449,12 +446,12 @@ public class TrieField extends PrimitiveFieldType { // code in next 2 lines is identical to: int v = NumericUtils.floatToSortableInt(Float.intBitsToFloat(toInt(arr))); int v = toInt(bytesRef.bytes, bytesRef.offset); if (v<0) v ^= 0x7fffffff; - NumericUtils.intToPrefixCoded(v, 0, bytes); + NumericUtils.intToPrefixCodedBytes(v, 0, bytes); break; } case LONG: //fallthrough! case DATE: - NumericUtils.longToPrefixCoded(toLong(bytesRef.bytes, bytesRef.offset), 0, bytes); + NumericUtils.longToPrefixCodedBytes(toLong(bytesRef.bytes, bytesRef.offset), 0, bytes); break; case DOUBLE: { // WARNING: Code Duplication! Keep in sync with o.a.l.util.NumericUtils! @@ -462,7 +459,7 @@ public class TrieField extends PrimitiveFieldType { // code in next 2 lines is identical to: long v = NumericUtils.doubleToSortableLong(Double.longBitsToDouble(toLong(arr))); long v = toLong(bytesRef.bytes, bytesRef.offset); if (v<0) v ^= 0x7fffffffffffffffL; - NumericUtils.longToPrefixCoded(v, 0, bytes); + NumericUtils.longToPrefixCodedBytes(v, 0, bytes); break; } default: diff --git a/solr/core/src/java/org/apache/solr/update/UpdateLog.java b/solr/core/src/java/org/apache/solr/update/UpdateLog.java index e291b646900..fcafb69ad73 100644 --- a/solr/core/src/java/org/apache/solr/update/UpdateLog.java +++ b/solr/core/src/java/org/apache/solr/update/UpdateLog.java @@ -917,10 +917,11 @@ public class UpdateLog implements PluginInfoInitialized { reader = oldLog.getReverseReader(); while (numUpdates < numRecordsToKeep) { - Object o = reader.next(); - if (o==null) break; + Object o = null; try { - + o = reader.next(); + if (o==null) break; + // should currently be a List List entry = (List)o; diff --git a/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionProcessorTest.java b/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionProcessorTest.java index 3e83c76735e..f3e9ee6609e 100644 --- a/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionProcessorTest.java +++ b/solr/core/src/test/org/apache/solr/cloud/OverseerCollectionProcessorTest.java @@ -19,10 +19,11 @@ package org.apache.solr.cloud; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; -import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.verify; import java.util.ArrayList; @@ -36,6 +37,8 @@ import java.util.Queue; import java.util.Set; import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.solrj.SolrResponse; +import org.apache.solr.cloud.DistributedQueue.QueueEvent; import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.cloud.ZkNodeProps; @@ -43,11 +46,13 @@ import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.params.CoreAdminParams; import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction; import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.StrUtils; import org.apache.solr.handler.component.ShardHandler; import org.apache.solr.handler.component.ShardRequest; import org.apache.solr.handler.component.ShardResponse; import org.easymock.Capture; +import org.easymock.EasyMock; import org.easymock.IAnswer; import org.eclipse.jetty.util.BlockingArrayQueue; import org.junit.After; @@ -71,12 +76,12 @@ public class OverseerCollectionProcessorTest extends SolrTestCaseJ4 { private OverseerCollectionProcessorToBeTested underTest; private Thread thread; - private Queue queue = new BlockingArrayQueue(); + private Queue queue = new BlockingArrayQueue(); private class OverseerCollectionProcessorToBeTested extends OverseerCollectionProcessor { - private boolean lastProcessMessageResult = true; + private SolrResponse lastProcessMessageResult; public OverseerCollectionProcessorToBeTested(ZkStateReader zkStateReader, String myId, ShardHandler shardHandler, String adminPath, @@ -85,7 +90,7 @@ public class OverseerCollectionProcessorTest extends SolrTestCaseJ4 { } @Override - protected boolean processMessage(ZkNodeProps message, String operation) { + protected SolrResponse processMessage(ZkNodeProps message, String operation) { lastProcessMessageResult = super.processMessage(message, operation); return lastProcessMessageResult; } @@ -147,11 +152,12 @@ public class OverseerCollectionProcessorTest extends SolrTestCaseJ4 { } }).anyTimes(); - workQueueMock.remove(); + workQueueMock.remove(anyObject(QueueEvent.class)); expectLastCall().andAnswer(new IAnswer() { @Override public Object answer() throws Throwable { - return queue.poll(); + queue.remove((QueueEvent)EasyMock.getCurrentArguments()[0]); + return null; } }).anyTimes(); @@ -273,7 +279,8 @@ public class OverseerCollectionProcessorTest extends SolrTestCaseJ4 { OverseerCollectionProcessor.MAX_SHARDS_PER_NODE, maxShardsPerNode.toString()); } - queue.add(ZkStateReader.toJSON(props)); + QueueEvent qe = new QueueEvent("id", ZkStateReader.toJSON(props), null); + queue.add(qe); } protected void verifySubmitCaptures(List submitCaptures, @@ -443,7 +450,9 @@ public class OverseerCollectionProcessorTest extends SolrTestCaseJ4 { waitForEmptyQueue(10000); - assertEquals(collectionExceptedToBeCreated, underTest.lastProcessMessageResult); + if (collectionExceptedToBeCreated) { + assertNotNull(underTest.lastProcessMessageResult.getResponse().toString(), underTest.lastProcessMessageResult); + } verify(shardHandlerMock); if (collectionExceptedToBeCreated) { diff --git a/solr/example/contexts/solr.xml b/solr/example/contexts/solr-jetty-context.xml similarity index 100% rename from solr/example/contexts/solr.xml rename to solr/example/contexts/solr-jetty-context.xml diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java index 4bda6bd7579..0e933dd9a48 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrQuery.java @@ -25,7 +25,10 @@ import org.apache.solr.common.params.StatsParams; import org.apache.solr.common.params.TermsParams; import org.apache.solr.common.util.DateUtil; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Locale; import java.util.regex.Pattern; @@ -44,6 +47,9 @@ public class SolrQuery extends ModifiableSolrParams return (this == asc) ? desc : asc; } } + + /** Maintains a map of current sorts */ + private List sortClauses; public SolrQuery() { super(); @@ -529,38 +535,230 @@ public class SolrQuery extends ModifiableSolrParams return this.get(HighlightParams.SIMPLE_POST, ""); } + /** + * Replaces the sort string with a single sort field. + * @deprecated Use {@link #setSort(SortClause)} instead, which is part + * of an api handling a wider range of sort specifications. + */ + @Deprecated public SolrQuery setSortField(String field, ORDER order) { this.remove(CommonParams.SORT); addValueToParam(CommonParams.SORT, toSortString(field, order)); return this; } + /** + * Adds a sort field to the end of the sort string. + * @deprecated Use {@link #addSort(SortClause)} instead, which is part + * of an api handling a wider range of sort specifications. + */ + @Deprecated public SolrQuery addSortField(String field, ORDER order) { return addValueToParam(CommonParams.SORT, toSortString(field, order)); } + /** + * Removes a sort field to the end of the sort string. + * @deprecated Use {@link #removeSort(SortClause)} instead, which is part + * of an api handling a wider range of sort specifications. + */ + @Deprecated public SolrQuery removeSortField(String field, ORDER order) { - String s = this.get(CommonParams.SORT); - String removeSort = toSortString(field, order); - if (s != null) { - String[] sorts = s.split(","); - s = join(sorts, ", ", removeSort); + String[] sorts = getSortFields(); + if (sorts != null) { + String removeSort = toSortString(field, order); + String s = join(sorts, ",", removeSort); if (s.length()==0) s=null; this.set(CommonParams.SORT, s); } return this; } + /** + * Gets an array of sort specifications. + * @deprecated Use {@link #getSorts()} instead, which is part + * of an api handling a wider range of sort specifications. + */ + @Deprecated public String[] getSortFields() { String s = getSortField(); if (s==null) return null; - return s.split(","); + return s.trim().split(", *"); } + /** + * Gets the raw sort field, as it will be sent to Solr. + *

+ * The returned sort field will always contain a serialized version + * of the sort string built using {@link #setSort(SortClause)}, + * {@link #addSort(SortClause)}, {@link #addOrUpdateSort(SortClause)}, + * {@link #removeSort(SortClause)}, {@link #clearSorts()} and + * {@link #setSorts(List)}. + */ public String getSortField() { return this.get(CommonParams.SORT); } + /** + * Clears current sort information. + * + * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery clearSorts() { + sortClauses = null; + serializeSorts(); + return this; + } + + /** + * Replaces the current sort information. + * + * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery setSorts(List value) { + sortClauses = new ArrayList(value); + serializeSorts(); + return this; + } + + /** + * Gets an a list of current sort clauses. + * + * @return an immutable list of current sort clauses + * @since 4.2 + */ + public List getSorts() { + if (sortClauses == null) return Collections.emptyList(); + else return Collections.unmodifiableList(sortClauses); + } + + /** + * Replaces the current sort information with a single sort clause + * + * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery setSort(String field, ORDER order) { + return setSort(new SortClause(field, order)); + } + + /** + * Replaces the current sort information with a single sort clause + * + * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery setSort(SortClause sortClause) { + clearSorts(); + return addSort(sortClause); + } + + /** + * Adds a single sort clause to the end of the current sort information. + * + * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery addSort(String field, ORDER order) { + return addSort(new SortClause(field, order)); + } + + /** + * Adds a single sort clause to the end of the query. + * + * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery addSort(SortClause sortClause) { + if (sortClauses == null) sortClauses = new ArrayList(); + sortClauses.add(sortClause); + serializeSorts(); + return this; + } + + /** + * Updates or adds a single sort clause to the query. + * If the field is already used for sorting, the order + * of the existing field is modified; otherwise, it is + * added to the end. + *

+ * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery addOrUpdateSort(String field, ORDER order) { + return addOrUpdateSort(new SortClause(field, order)); + } + + /** + * Updates or adds a single sort field specification to the current sort + * information. If the sort field already exist in the sort information map, + * it's position is unchanged and the sort order is set; if it does not exist, + * it is appended at the end with the specified order.. + * + * @return the modified SolrQuery object, for easy chaining + * @since 4.2 + */ + public SolrQuery addOrUpdateSort(SortClause sortClause) { + if (sortClauses != null) { + for (int index=0 ; index 0) sb.append(","); + sb.append(sortClause.getItem()); + sb.append(" "); + sb.append(sortClause.getOrder()); + } + set(CommonParams.SORT, sb.toString()); + } + } + public void setGetFieldStatistics( boolean v ) { this.set( StatsParams.STATS, v ); @@ -823,13 +1021,126 @@ public class SolrQuery extends ModifiableSolrParams private String join(String[] vals, String sep, String removeVal) { StringBuilder sb = new StringBuilder(); for (int i=0; i 0) { sb.append(sep); } + sb.append(vals[i]); } } return sb.toString().trim(); } + + /** + * A single sort clause, encapsulating what to sort and the sort order. + *

+ * The item specified can be "anything sortable" by solr; some examples + * include a simple field name, the constant string {@code score}, and functions + * such as {@code sum(x_f, y_f)}. + *

+ * A SortClause can be created through different mechanisms: + *


+   * new SortClause("product", SolrQuery.ORDER.asc);
+   * new SortClause("product", "asc");
+   * SortClause.asc("product");
+   * SortClause.desc("product");
+   * 
+ */ + public static class SortClause implements java.io.Serializable { + + private static final long serialVersionUID = 1L; + + private final String item; + private final ORDER order; + + /** + * Creates a SortClause based on item and order + * @param item item to sort on + * @param order direction to sort + */ + public SortClause(String item, ORDER order) { + this.item = item; + this.order = order; + } + + /** + * Creates a SortClause based on item and order + * @param item item to sort on + * @param order string value for direction to sort + */ + public SortClause(String item, String order) { + this(item, ORDER.valueOf(order)); + } + + /** + * Creates an ascending SortClause for an item + * @param item item to sort on + */ + public static SortClause create (String item, ORDER order) { + return new SortClause(item, order); + } + + /** + * Creates a SortClause based on item and order + * @param item item to sort on + * @param order string value for direction to sort + */ + public static SortClause create(String item, String order) { + return new SortClause(item, ORDER.valueOf(order)); + } + + /** + * Creates an ascending SortClause for an item + * @param item item to sort on + */ + public static SortClause asc (String item) { + return new SortClause(item, ORDER.asc); + } + + /** + * Creates a decending SortClause for an item + * @param item item to sort on + */ + public static SortClause desc (String item) { + return new SortClause(item, ORDER.desc); + } + + /** + * Gets the item to sort, typically a function or a fieldname + * @return item to sort + */ + public String getItem() { + return item; + } + + /** + * Gets the order to sort + * @return order to sort + */ + public ORDER getOrder() { + return order; + } + + public boolean equals(Object other){ + if (this == other) return true; + if (!(other instanceof SortClause)) return false; + final SortClause that = (SortClause) other; + return this.getItem().equals(that.getItem()) && this.getOrder().equals(that.getOrder()); + } + + public int hashCode(){ + return this.getItem().hashCode(); + } + + /** + * Gets a human readable description of the sort clause. + *

+ * The returned string is not suitable for passing to Solr, + * but may be useful in debug output and the like. + * @return a description of the current sort clause + */ + public String toString() { + return "[" + getClass().getSimpleName() + ": item=" + getItem() + "; order=" + getOrder() + "]"; + } + } } diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java index a4d0e4f3806..4d4065ccf86 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrResponse.java @@ -17,19 +17,47 @@ package org.apache.solr.client.solrj; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.util.NamedList; /** * - * + * * @since solr 1.3 */ -public abstract class SolrResponse implements Serializable -{ +public abstract class SolrResponse implements Serializable { public abstract long getElapsedTime(); - public abstract void setResponse( NamedList rsp ); + + public abstract void setResponse(NamedList rsp); + public abstract NamedList getResponse(); + + public static byte[] serializable(SolrResponse response) { + try { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + ObjectOutputStream outputStream = new ObjectOutputStream(byteStream); + outputStream.writeObject(response); + return byteStream.toByteArray(); + } catch (Exception e) { + throw new SolrException(ErrorCode.SERVER_ERROR, e); + } + } + + public static SolrResponse deserialize(byte[] bytes) { + try { + ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); + ObjectInputStream inputStream = new ObjectInputStream(byteStream); + return (SolrResponse) inputStream.readObject(); + } catch (Exception e) { + throw new SolrException(ErrorCode.SERVER_ERROR, e); + } + } } diff --git a/solr/solrj/src/java/org/apache/solr/common/SolrDocument.java b/solr/solrj/src/java/org/apache/solr/common/SolrDocument.java index 1a7c9a8dd67..eae68355b77 100644 --- a/solr/solrj/src/java/org/apache/solr/common/SolrDocument.java +++ b/solr/solrj/src/java/org/apache/solr/common/SolrDocument.java @@ -108,7 +108,13 @@ public class SolrDocument implements Map, Iterable, Iterable, IterableSparkline: 1,4,6,6,8,5,3,5

-* $('.sparkline').sparkline(); -* There must be no spaces in the enclosed data set -* -* Otherwise values must be an array of numbers or null values -*

Sparkline: This text replaced if the browser is compatible

-* $('#sparkline1').sparkline([1,4,6,6,8,5,3,5]) -* $('#sparkline2').sparkline([1,4,6,null,null,5,3,5]) -* -* Values can also be specified in an HTML comment, or as a values attribute: -*

Sparkline:

-*

Sparkline:

-* $('.sparkline').sparkline(); -* -* For line charts, x values can also be specified: -*

Sparkline: 1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5

-* $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ]) -* -* By default, options should be passed in as teh second argument to the sparkline function: -* $('.sparkline').sparkline([1,2,3,4], {type: 'bar'}) -* -* Options can also be set by passing them on the tag itself. This feature is disabled by default though -* as there's a slight performance overhead: -* $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true}) -*

Sparkline: loading

-* Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix) -* -* Supported options: -* lineColor - Color of the line used for the chart -* fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart -* width - Width of the chart - Defaults to 3 times the number of values in pixels -* height - Height of the chart - Defaults to the height of the containing element -* chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied -* chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied -* chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax -* chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied -* chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied -* composite - If true then don't erase any existing chart attached to the tag, but draw -* another chart over the top - Note that width and height are ignored if an -* existing chart is detected. -* tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values' -* enableTagOptions - Whether to check tags for sparkline options -* tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark' -* disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a -* hidden dom element, avoding a browser reflow -* disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled, -* making the plugin perform much like it did in 1.x -* disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled) -* disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled -* defaults to false (highlights enabled) -* highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase -* tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body -* tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied -* tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis -* tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis -* tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip -* callback is given arguments of (sparkline, options, fields) -* tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title -* tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries) -* to control the format of the tooltip -* tooltipPrefix - A string to prepend to each field displayed in a tooltip -* tooltipSuffix - A string to append to each field displayed in a tooltip -* tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true) -* tooltipValueLookups - An object or range map to map field values to tooltip strings -* (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win") -* numberFormatter - Optional callback for formatting numbers in tooltips -* numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to "," -* numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "." -* numberDigitGroupCount - Number of digits between group separator - Defaults to 3 -* -* There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default), -* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box' -* line - Line chart. Options: -* spotColor - Set to '' to not end each line in a circular spot -* minSpotColor - If set, color of spot at minimum value -* maxSpotColor - If set, color of spot at maximum value -* spotRadius - Radius in pixels -* lineWidth - Width of line in pixels -* normalRangeMin -* normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal" -* or expected range of values -* normalRangeColor - Color to use for the above bar -* drawNormalOnTop - Draw the normal range above the chart fill color if true -* defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart -* highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable -* highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable -* valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map -* -* bar - Bar chart. Options: -* barColor - Color of bars for postive values -* negBarColor - Color of bars for negative values -* zeroColor - Color of bars with zero values -* nullColor - Color of bars with null values - Defaults to omitting the bar entirely -* barWidth - Width of bars in pixels -* colorMap - Optional mappnig of values to colors to override the *BarColor values above -* can be an Array of values to control the color of individual bars or a range map -* to specify colors for individual ranges of values -* barSpacing - Gap between bars in pixels -* zeroAxis - Centers the y-axis around zero if true -* -* tristate - Charts values of win (>0), lose (<0) or draw (=0) -* posBarColor - Color of win values -* negBarColor - Color of lose values -* zeroBarColor - Color of draw values -* barWidth - Width of bars in pixels -* barSpacing - Gap between bars in pixels -* colorMap - Optional mappnig of values to colors to override the *BarColor values above -* can be an Array of values to control the color of individual bars or a range map -* to specify colors for individual ranges of values -* -* discrete - Options: -* lineHeight - Height of each line in pixels - Defaults to 30% of the graph height -* thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor -* thresholdColor -* -* bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ... -* options: -* targetColor - The color of the vertical target marker -* targetWidth - The width of the target marker in pixels -* performanceColor - The color of the performance measure horizontal bar -* rangeColors - Colors to use for each qualitative range background color -* -* pie - Pie chart. Options: -* sliceColors - An array of colors to use for pie slices -* offset - Angle in degrees to offset the first slice - Try -90 or +90 -* borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border) -* borderColor - Color to use for the pie chart border - Defaults to #000 -* -* box - Box plot. Options: -* raw - Set to true to supply pre-computed plot points as values -* values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier -* When set to false you can supply any number of values and the box plot will -* be computed for you. Default is false. -* showOutliers - Set to true (default) to display outliers as circles -* outlierIQR - Interquartile range used to determine outliers. Default 1.5 -* boxLineColor - Outline color of the box -* boxFillColor - Fill color for the box -* whiskerColor - Line color used for whiskers -* outlierLineColor - Outline color of outlier circles -* outlierFillColor - Fill color of the outlier circles -* spotRadius - Radius of outlier circles -* medianColor - Line color of the median line -* target - Draw a target cross hair at the supplied value (default undefined) -* -* -* -* Examples: -* $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false }); -* $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 }); -* $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }): -* $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' }); -* $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' }); -* $('#pie').sparkline([1,1,2], { type:'pie' }); -*/ - -/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */ - -(function(factory) { - if(typeof define === 'function' && define.amd) { - define(['jquery'], factory); - } - else { - factory(jQuery); - } -} -(function($) { - 'use strict'; - - var UNSET_OPTION = {}, - getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues, - remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap, - MouseHandler, Tooltip, barHighlightMixin, - line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles, - VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0; - - /** - * Default configuration settings - */ - getDefaults = function () { - return { - // Settings common to most/all chart types - common: { - type: 'line', - lineColor: '#00f', - fillColor: '#cdf', - defaultPixelsPerValue: 3, - width: 'auto', - height: 'auto', - composite: false, - tagValuesAttribute: 'values', - tagOptionsPrefix: 'spark', - enableTagOptions: false, - enableHighlight: true, - highlightLighten: 1.4, - tooltipSkipNull: true, - tooltipPrefix: '', - tooltipSuffix: '', - disableHiddenCheck: false, - numberFormatter: false, - numberDigitGroupCount: 3, - numberDigitGroupSep: ',', - numberDecimalMark: '.', - disableTooltips: false, - disableInteraction: false - }, - // Defaults for line charts - line: { - spotColor: '#f80', - highlightSpotColor: '#5f5', - highlightLineColor: '#f22', - spotRadius: 1.5, - minSpotColor: '#f80', - maxSpotColor: '#f80', - lineWidth: 1, - normalRangeMin: undefined, - normalRangeMax: undefined, - normalRangeColor: '#ccc', - drawNormalOnTop: false, - chartRangeMin: undefined, - chartRangeMax: undefined, - chartRangeMinX: undefined, - chartRangeMaxX: undefined, - tooltipFormat: new SPFormat(' {{prefix}}{{y}}{{suffix}}') - }, - // Defaults for bar charts - bar: { - barColor: '#3366cc', - negBarColor: '#f44', - stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00', - '#dd4477', '#0099c6', '#990099'], - zeroColor: undefined, - nullColor: undefined, - zeroAxis: true, - barWidth: 4, - barSpacing: 1, - chartRangeMax: undefined, - chartRangeMin: undefined, - chartRangeClip: false, - colorMap: undefined, - tooltipFormat: new SPFormat(' {{prefix}}{{value}}{{suffix}}') - }, - // Defaults for tristate charts - tristate: { - barWidth: 4, - barSpacing: 1, - posBarColor: '#6f6', - negBarColor: '#f44', - zeroBarColor: '#999', - colorMap: {}, - tooltipFormat: new SPFormat(' {{value:map}}'), - tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } } - }, - // Defaults for discrete charts - discrete: { - lineHeight: 'auto', - thresholdColor: undefined, - thresholdValue: 0, - chartRangeMax: undefined, - chartRangeMin: undefined, - chartRangeClip: false, - tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}') - }, - // Defaults for bullet charts - bullet: { - targetColor: '#f33', - targetWidth: 3, // width of the target bar in pixels - performanceColor: '#33f', - rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'], - base: undefined, // set this to a number to change the base start number - tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'), - tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} } - }, - // Defaults for pie charts - pie: { - offset: 0, - sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00', - '#dd4477', '#0099c6', '#990099'], - borderWidth: 0, - borderColor: '#000', - tooltipFormat: new SPFormat(' {{value}} ({{percent.1}}%)') - }, - // Defaults for box plots - box: { - raw: false, - boxLineColor: '#000', - boxFillColor: '#cdf', - whiskerColor: '#000', - outlierLineColor: '#333', - outlierFillColor: '#fff', - medianColor: '#f00', - showOutliers: true, - outlierIQR: 1.5, - spotRadius: 1.5, - target: undefined, - targetColor: '#4a2', - chartRangeMax: undefined, - chartRangeMin: undefined, - tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'), - tooltipFormatFieldlistKey: 'field', - tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median', - uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier', - lw: 'Left Whisker', rw: 'Right Whisker'} } - } - }; - }; - - // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname - defaultStyles = '.jqstooltip { ' + - 'position: absolute;' + - 'left: 0px;' + - 'top: 0px;' + - 'visibility: hidden;' + - 'background: rgb(0, 0, 0) transparent;' + - 'background-color: rgba(0,0,0,0.6);' + - 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' + - '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' + - 'color: white;' + - 'font: 10px arial, san serif;' + - 'text-align: left;' + - 'white-space: nowrap;' + - 'padding: 5px;' + - 'border: 1px solid white;' + - 'z-index: 10000;' + - '}' + - '.jqsfield { ' + - 'color: white;' + - 'font: 10px arial, san serif;' + - 'text-align: left;' + - '}'; - - /** - * Utilities - */ - - createClass = function (/* [baseclass, [mixin, ...]], definition */) { - var Class, args; - Class = function () { - this.init.apply(this, arguments); - }; - if (arguments.length > 1) { - if (arguments[0]) { - Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]); - Class._super = arguments[0].prototype; - } else { - Class.prototype = arguments[arguments.length - 1]; - } - if (arguments.length > 2) { - args = Array.prototype.slice.call(arguments, 1, -1); - args.unshift(Class.prototype); - $.extend.apply($, args); - } - } else { - Class.prototype = arguments[0]; - } - Class.prototype.cls = Class; - return Class; - }; - - /** - * Wraps a format string for tooltips - * {{x}} - * {{x.2} - * {{x:months}} - */ - $.SPFormatClass = SPFormat = createClass({ - fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g, - precre: /(\w+)\.(\d+)/, - - init: function (format, fclass) { - this.format = format; - this.fclass = fclass; - }, - - render: function (fieldset, lookups, options) { - var self = this, - fields = fieldset, - match, token, lookupkey, fieldvalue, prec; - return this.format.replace(this.fre, function () { - var lookup; - token = arguments[1]; - lookupkey = arguments[3]; - match = self.precre.exec(token); - if (match) { - prec = match[2]; - token = match[1]; - } else { - prec = false; - } - fieldvalue = fields[token]; - if (fieldvalue === undefined) { - return ''; - } - if (lookupkey && lookups && lookups[lookupkey]) { - lookup = lookups[lookupkey]; - if (lookup.get) { // RangeMap - return lookups[lookupkey].get(fieldvalue) || fieldvalue; - } else { - return lookups[lookupkey][fieldvalue] || fieldvalue; - } - } - if (isNumber(fieldvalue)) { - if (options.get('numberFormatter')) { - fieldvalue = options.get('numberFormatter')(fieldvalue); - } else { - fieldvalue = formatNumber(fieldvalue, prec, - options.get('numberDigitGroupCount'), - options.get('numberDigitGroupSep'), - options.get('numberDecimalMark')); - } - } - return fieldvalue; - }); - } - }); - - // convience method to avoid needing the new operator - $.spformat = function(format, fclass) { - return new SPFormat(format, fclass); - }; - - clipval = function (val, min, max) { - if (val < min) { - return min; - } - if (val > max) { - return max; - } - return val; - }; - - quartile = function (values, q) { - var vl; - if (q === 2) { - vl = Math.floor(values.length / 2); - return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2; - } else { - if (values.length % 2 ) { // odd - vl = (values.length * q + q) / 4; - return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1]; - } else { //even - vl = (values.length * q + 2) / 4; - return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1]; - - } - } - }; - - normalizeValue = function (val) { - var nf; - switch (val) { - case 'undefined': - val = undefined; - break; - case 'null': - val = null; - break; - case 'true': - val = true; - break; - case 'false': - val = false; - break; - default: - nf = parseFloat(val); - if (val == nf) { - val = nf; - } - } - return val; - }; - - normalizeValues = function (vals) { - var i, result = []; - for (i = vals.length; i--;) { - result[i] = normalizeValue(vals[i]); - } - return result; - }; - - remove = function (vals, filter) { - var i, vl, result = []; - for (i = 0, vl = vals.length; i < vl; i++) { - if (vals[i] !== filter) { - result.push(vals[i]); - } - } - return result; - }; - - isNumber = function (num) { - return !isNaN(parseFloat(num)) && isFinite(num); - }; - - formatNumber = function (num, prec, groupsize, groupsep, decsep) { - var p, i; - num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split(''); - p = (p = $.inArray('.', num)) < 0 ? num.length : p; - if (p < num.length) { - num[p] = decsep; - } - for (i = p - groupsize; i > 0; i -= groupsize) { - num.splice(i, 0, groupsep); - } - return num.join(''); - }; - - // determine if all values of an array match a value - // returns true if the array is empty - all = function (val, arr, ignoreNull) { - var i; - for (i = arr.length; i--; ) { - if (ignoreNull && arr[i] === null) continue; - if (arr[i] !== val) { - return false; - } - } - return true; - }; - - // sums the numeric values in an array, ignoring other values - sum = function (vals) { - var total = 0, i; - for (i = vals.length; i--;) { - total += typeof vals[i] === 'number' ? vals[i] : 0; - } - return total; - }; - - ensureArray = function (val) { - return $.isArray(val) ? val : [val]; - }; - - // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/ - addCSS = function(css) { - var tag; - //if ('\v' == 'v') /* ie only */ { - if (document.createStyleSheet) { - document.createStyleSheet().cssText = css; - } else { - tag = document.createElement('style'); - tag.type = 'text/css'; - document.getElementsByTagName('head')[0].appendChild(tag); - tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css; - } - }; - - // Provide a cross-browser interface to a few simple drawing primitives - $.fn.simpledraw = function (width, height, useExisting, interact) { - var target, mhandler; - if (useExisting && (target = this.data('_jqs_vcanvas'))) { - return target; - } - if (width === undefined) { - width = $(this).innerWidth(); - } - if (height === undefined) { - height = $(this).innerHeight(); - } - if ($.browser.hasCanvas) { - target = new VCanvas_canvas(width, height, this, interact); - } else if ($.browser.msie) { - target = new VCanvas_vml(width, height, this); - } else { - return false; - } - mhandler = $(this).data('_jqs_mhandler'); - if (mhandler) { - mhandler.registerCanvas(target); - } - return target; - }; - - $.fn.cleardraw = function () { - var target = this.data('_jqs_vcanvas'); - if (target) { - target.reset(); - } - }; - - $.RangeMapClass = RangeMap = createClass({ - init: function (map) { - var key, range, rangelist = []; - for (key in map) { - if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) { - range = key.split(':'); - range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]); - range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]); - range[2] = map[key]; - rangelist.push(range); - } - } - this.map = map; - this.rangelist = rangelist || false; - }, - - get: function (value) { - var rangelist = this.rangelist, - i, range, result; - if ((result = this.map[value]) !== undefined) { - return result; - } - if (rangelist) { - for (i = rangelist.length; i--;) { - range = rangelist[i]; - if (range[0] <= value && range[1] >= value) { - return range[2]; - } - } - } - return undefined; - } - }); - - // Convenience function - $.range_map = function(map) { - return new RangeMap(map); - }; - - MouseHandler = createClass({ - init: function (el, options) { - var $el = $(el); - this.$el = $el; - this.options = options; - this.currentPageX = 0; - this.currentPageY = 0; - this.el = el; - this.splist = []; - this.tooltip = null; - this.over = false; - this.displayTooltips = !options.get('disableTooltips'); - this.highlightEnabled = !options.get('disableHighlight'); - }, - - registerSparkline: function (sp) { - this.splist.push(sp); - if (this.over) { - this.updateDisplay(); - } - }, - - registerCanvas: function (canvas) { - var $canvas = $(canvas.canvas); - this.canvas = canvas; - this.$canvas = $canvas; - $canvas.mouseenter($.proxy(this.mouseenter, this)); - $canvas.mouseleave($.proxy(this.mouseleave, this)); - $canvas.click($.proxy(this.mouseclick, this)); - }, - - reset: function (removeTooltip) { - this.splist = []; - if (this.tooltip && removeTooltip) { - this.tooltip.remove(); - this.tooltip = undefined; - } - }, - - mouseclick: function (e) { - var clickEvent = $.Event('sparklineClick'); - clickEvent.originalEvent = e; - clickEvent.sparklines = this.splist; - this.$el.trigger(clickEvent); - }, - - mouseenter: function (e) { - $(document.body).unbind('mousemove.jqs'); - $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this)); - this.over = true; - this.currentPageX = e.pageX; - this.currentPageY = e.pageY; - this.currentEl = e.target; - if (!this.tooltip && this.displayTooltips) { - this.tooltip = new Tooltip(this.options); - this.tooltip.updatePosition(e.pageX, e.pageY); - } - this.updateDisplay(); - }, - - mouseleave: function () { - $(document.body).unbind('mousemove.jqs'); - var splist = this.splist, - spcount = splist.length, - needsRefresh = false, - sp, i; - this.over = false; - this.currentEl = null; - - if (this.tooltip) { - this.tooltip.remove(); - this.tooltip = null; - } - - for (i = 0; i < spcount; i++) { - sp = splist[i]; - if (sp.clearRegionHighlight()) { - needsRefresh = true; - } - } - - if (needsRefresh) { - this.canvas.render(); - } - }, - - mousemove: function (e) { - this.currentPageX = e.pageX; - this.currentPageY = e.pageY; - this.currentEl = e.target; - if (this.tooltip) { - this.tooltip.updatePosition(e.pageX, e.pageY); - } - this.updateDisplay(); - }, - - updateDisplay: function () { - var splist = this.splist, - spcount = splist.length, - needsRefresh = false, - offset = this.$canvas.offset(), - localX = this.currentPageX - offset.left, - localY = this.currentPageY - offset.top, - tooltiphtml, sp, i, result, changeEvent; - if (!this.over) { - return; - } - for (i = 0; i < spcount; i++) { - sp = splist[i]; - result = sp.setRegionHighlight(this.currentEl, localX, localY); - if (result) { - needsRefresh = true; - } - } - if (needsRefresh) { - changeEvent = $.Event('sparklineRegionChange'); - changeEvent.sparklines = this.splist; - this.$el.trigger(changeEvent); - if (this.tooltip) { - tooltiphtml = ''; - for (i = 0; i < spcount; i++) { - sp = splist[i]; - tooltiphtml += sp.getCurrentRegionTooltip(); - } - this.tooltip.setContent(tooltiphtml); - } - if (!this.disableHighlight) { - this.canvas.render(); - } - } - if (result === null) { - this.mouseleave(); - } - } - }); - - - Tooltip = createClass({ - sizeStyle: 'position: static !important;' + - 'display: block !important;' + - 'visibility: hidden !important;' + - 'float: left !important;', - - init: function (options) { - var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'), - sizetipStyle = this.sizeStyle, - offset; - this.container = options.get('tooltipContainer') || document.body; - this.tooltipOffsetX = options.get('tooltipOffsetX', 10); - this.tooltipOffsetY = options.get('tooltipOffsetY', 12); - // remove any previous lingering tooltip - $('#jqssizetip').remove(); - $('#jqstooltip').remove(); - this.sizetip = $('
', { - id: 'jqssizetip', - style: sizetipStyle, - 'class': tooltipClassname - }); - this.tooltip = $('
', { - id: 'jqstooltip', - 'class': tooltipClassname - }).appendTo(this.container); - // account for the container's location - offset = this.tooltip.offset(); - this.offsetLeft = offset.left; - this.offsetTop = offset.top; - this.hidden = true; - $(window).unbind('resize.jqs scroll.jqs'); - $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this)); - this.updateWindowDims(); - }, - - updateWindowDims: function () { - this.scrollTop = $(window).scrollTop(); - this.scrollLeft = $(window).scrollLeft(); - this.scrollRight = this.scrollLeft + $(window).width(); - this.updatePosition(); - }, - - getSize: function (content) { - this.sizetip.html(content).appendTo(this.container); - this.width = this.sizetip.width() + 1; - this.height = this.sizetip.height(); - this.sizetip.remove(); - }, - - setContent: function (content) { - if (!content) { - this.tooltip.css('visibility', 'hidden'); - this.hidden = true; - return; - } - this.getSize(content); - this.tooltip.html(content) - .css({ - 'width': this.width, - 'height': this.height, - 'visibility': 'visible' - }); - if (this.hidden) { - this.hidden = false; - this.updatePosition(); - } - }, - - updatePosition: function (x, y) { - if (x === undefined) { - if (this.mousex === undefined) { - return; - } - x = this.mousex - this.offsetLeft; - y = this.mousey - this.offsetTop; - - } else { - this.mousex = x = x - this.offsetLeft; - this.mousey = y = y - this.offsetTop; - } - if (!this.height || !this.width || this.hidden) { - return; - } - - y -= this.height + this.tooltipOffsetY; - x += this.tooltipOffsetX; - - if (y < this.scrollTop) { - y = this.scrollTop; - } - if (x < this.scrollLeft) { - x = this.scrollLeft; - } else if (x + this.width > this.scrollRight) { - x = this.scrollRight - this.width; - } - - this.tooltip.css({ - 'left': x, - 'top': y - }); - }, - - remove: function () { - this.tooltip.remove(); - this.sizetip.remove(); - this.sizetip = this.tooltip = undefined; - $(window).unbind('resize.jqs scroll.jqs'); - } - }); - - initStyles = function() { - addCSS(defaultStyles); - }; - - $(initStyles); - - pending = []; - $.fn.sparkline = function (userValues, userOptions) { - return this.each(function () { - var options = new $.fn.sparkline.options(this, userOptions), - $this = $(this), - render, i; - render = function () { - var values, width, height, tmp, mhandler, sp, vals; - if (userValues === 'html' || userValues === undefined) { - vals = this.getAttribute(options.get('tagValuesAttribute')); - if (vals === undefined || vals === null) { - vals = $this.html(); - } - values = vals.replace(/(^\s*\s*$)|\s+/g, '').split(','); - } else { - values = userValues; - } - - width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width'); - if (options.get('height') === 'auto') { - if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) { - // must be a better way to get the line height - tmp = document.createElement('span'); - tmp.innerHTML = 'a'; - $this.html(tmp); - height = $(tmp).innerHeight() || $(tmp).height(); - $(tmp).remove(); - tmp = null; - } - } else { - height = options.get('height'); - } - - if (!options.get('disableInteraction')) { - mhandler = $.data(this, '_jqs_mhandler'); - if (!mhandler) { - mhandler = new MouseHandler(this, options); - $.data(this, '_jqs_mhandler', mhandler); - } else if (!options.get('composite')) { - mhandler.reset(); - } - } else { - mhandler = false; - } - - if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) { - if (!$.data(this, '_jqs_errnotify')) { - alert('Attempted to attach a composite sparkline to an element with no existing sparkline'); - $.data(this, '_jqs_errnotify', true); - } - return; - } - - sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height); - - sp.render(); - - if (mhandler) { - mhandler.registerSparkline(sp); - } - }; - // jQuery 1.3.0 completely changed the meaning of :hidden :-/ - if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || ($.fn.jquery < '1.3.0' && $(this).parents().is(':hidden')) || !$(this).parents('body').length) { - if (!options.get('composite') && $.data(this, '_jqs_pending')) { - // remove any existing references to the element - for (i = pending.length; i; i--) { - if (pending[i - 1][0] == this) { - pending.splice(i - 1, 1); - } - } - } - pending.push([this, render]); - $.data(this, '_jqs_pending', true); - } else { - render.call(this); - } - }); - }; - - $.fn.sparkline.defaults = getDefaults(); - - - $.sparkline_display_visible = function () { - var el, i, pl; - var done = []; - for (i = 0, pl = pending.length; i < pl; i++) { - el = pending[i][0]; - if ($(el).is(':visible') && !$(el).parents().is(':hidden')) { - pending[i][1].call(el); - $.data(pending[i][0], '_jqs_pending', false); - done.push(i); - } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) { - // element has been inserted and removed from the DOM - // If it was not yet inserted into the dom then the .data request - // will return true. - // removing from the dom causes the data to be removed. - $.data(pending[i][0], '_jqs_pending', false); - done.push(i); - } - } - for (i = done.length; i; i--) { - pending.splice(done[i - 1], 1); - } - }; - - - /** - * User option handler - */ - $.fn.sparkline.options = createClass({ - init: function (tag, userOptions) { - var extendedOptions, defaults, base, tagOptionType; - this.userOptions = userOptions = userOptions || {}; - this.tag = tag; - this.tagValCache = {}; - defaults = $.fn.sparkline.defaults; - base = defaults.common; - this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix); - - tagOptionType = this.getTagSetting('type'); - if (tagOptionType === UNSET_OPTION) { - extendedOptions = defaults[userOptions.type || base.type]; - } else { - extendedOptions = defaults[tagOptionType]; - } - this.mergedOptions = $.extend({}, base, extendedOptions, userOptions); - }, - - - getTagSetting: function (key) { - var prefix = this.tagOptionsPrefix, - val, i, pairs, keyval; - if (prefix === false || prefix === undefined) { - return UNSET_OPTION; - } - if (this.tagValCache.hasOwnProperty(key)) { - val = this.tagValCache.key; - } else { - val = this.tag.getAttribute(prefix + key); - if (val === undefined || val === null) { - val = UNSET_OPTION; - } else if (val.substr(0, 1) === '[') { - val = val.substr(1, val.length - 2).split(','); - for (i = val.length; i--;) { - val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, '')); - } - } else if (val.substr(0, 1) === '{') { - pairs = val.substr(1, val.length - 2).split(','); - val = {}; - for (i = pairs.length; i--;) { - keyval = pairs[i].split(':', 2); - val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, '')); - } - } else { - val = normalizeValue(val); - } - this.tagValCache.key = val; - } - return val; - }, - - get: function (key, defaultval) { - var tagOption = this.getTagSetting(key), - result; - if (tagOption !== UNSET_OPTION) { - return tagOption; - } - return (result = this.mergedOptions[key]) === undefined ? defaultval : result; - } - }); - - - $.fn.sparkline._base = createClass({ - disabled: false, - - init: function (el, values, options, width, height) { - this.el = el; - this.$el = $(el); - this.values = values; - this.options = options; - this.width = width; - this.height = height; - this.currentRegion = undefined; - }, - - /** - * Setup the canvas - */ - initTarget: function () { - var interactive = !this.options.get('disableInteraction'); - if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) { - this.disabled = true; - } else { - this.canvasWidth = this.target.pixelWidth; - this.canvasHeight = this.target.pixelHeight; - } - }, - - /** - * Actually render the chart to the canvas - */ - render: function () { - if (this.disabled) { - this.el.innerHTML = ''; - return false; - } - return true; - }, - - /** - * Return a region id for a given x/y co-ordinate - */ - getRegion: function (x, y) { - }, - - /** - * Highlight an item based on the moused-over x,y co-ordinate - */ - setRegionHighlight: function (el, x, y) { - var currentRegion = this.currentRegion, - highlightEnabled = !this.options.get('disableHighlight'), - newRegion; - if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) { - return null; - } - newRegion = this.getRegion(el, x, y); - if (currentRegion !== newRegion) { - if (currentRegion !== undefined && highlightEnabled) { - this.removeHighlight(); - } - this.currentRegion = newRegion; - if (newRegion !== undefined && highlightEnabled) { - this.renderHighlight(); - } - return true; - } - return false; - }, - - /** - * Reset any currently highlighted item - */ - clearRegionHighlight: function () { - if (this.currentRegion !== undefined) { - this.removeHighlight(); - this.currentRegion = undefined; - return true; - } - return false; - }, - - renderHighlight: function () { - this.changeHighlight(true); - }, - - removeHighlight: function () { - this.changeHighlight(false); - }, - - changeHighlight: function (highlight) {}, - - /** - * Fetch the HTML to display as a tooltip - */ - getCurrentRegionTooltip: function () { - var options = this.options, - header = '', - entries = [], - fields, formats, formatlen, fclass, text, i, - showFields, showFieldsKey, newFields, fv, - formatter, format, fieldlen, j; - if (this.currentRegion === undefined) { - return ''; - } - fields = this.getCurrentRegionFields(); - formatter = options.get('tooltipFormatter'); - if (formatter) { - return formatter(this, options, fields); - } - if (options.get('tooltipChartTitle')) { - header += '
' + options.get('tooltipChartTitle') + '
\n'; - } - formats = this.options.get('tooltipFormat'); - if (!formats) { - return ''; - } - if (!$.isArray(formats)) { - formats = [formats]; - } - if (!$.isArray(fields)) { - fields = [fields]; - } - showFields = this.options.get('tooltipFormatFieldlist'); - showFieldsKey = this.options.get('tooltipFormatFieldlistKey'); - if (showFields && showFieldsKey) { - // user-selected ordering of fields - newFields = []; - for (i = fields.length; i--;) { - fv = fields[i][showFieldsKey]; - if ((j = $.inArray(fv, showFields)) != -1) { - newFields[j] = fields[i]; - } - } - fields = newFields; - } - formatlen = formats.length; - fieldlen = fields.length; - for (i = 0; i < formatlen; i++) { - format = formats[i]; - if (typeof format === 'string') { - format = new SPFormat(format); - } - fclass = format.fclass || 'jqsfield'; - for (j = 0; j < fieldlen; j++) { - if (!fields[j].isNull || !options.get('tooltipSkipNull')) { - $.extend(fields[j], { - prefix: options.get('tooltipPrefix'), - suffix: options.get('tooltipSuffix') - }); - text = format.render(fields[j], options.get('tooltipValueLookups'), options); - entries.push('
' + text + '
'); - } - } - } - if (entries.length) { - return header + entries.join('\n'); - } - return ''; - }, - - getCurrentRegionFields: function () {}, - - calcHighlightColor: function (color, options) { - var highlightColor = options.get('highlightColor'), - lighten = options.get('highlightLighten'), - parse, mult, rgbnew, i; - if (highlightColor) { - return highlightColor; - } - if (lighten) { - // extract RGB values - parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color); - if (parse) { - rgbnew = []; - mult = color.length === 4 ? 16 : 1; - for (i = 0; i < 3; i++) { - rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255); - } - return 'rgb(' + rgbnew.join(',') + ')'; - } - - } - return color; - } - - }); - - barHighlightMixin = { - changeHighlight: function (highlight) { - var currentRegion = this.currentRegion, - target = this.target, - shapeids = this.regionShapes[currentRegion], - newShapes; - // will be null if the region value was null - if (shapeids) { - newShapes = this.renderRegion(currentRegion, highlight); - if ($.isArray(newShapes) || $.isArray(shapeids)) { - target.replaceWithShapes(shapeids, newShapes); - this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) { - return newShape.id; - }); - } else { - target.replaceWithShape(shapeids, newShapes); - this.regionShapes[currentRegion] = newShapes.id; - } - } - }, - - render: function () { - var values = this.values, - target = this.target, - regionShapes = this.regionShapes, - shapes, ids, i, j; - - if (!this.cls._super.render.call(this)) { - return; - } - for (i = values.length; i--;) { - shapes = this.renderRegion(i); - if (shapes) { - if ($.isArray(shapes)) { - ids = []; - for (j = shapes.length; j--;) { - shapes[j].append(); - ids.push(shapes[j].id); - } - regionShapes[i] = ids; - } else { - shapes.append(); - regionShapes[i] = shapes.id; // store just the shapeid - } - } else { - // null value - regionShapes[i] = null; - } - } - target.render(); - } - }; - - /** - * Line charts - */ - $.fn.sparkline.line = line = createClass($.fn.sparkline._base, { - type: 'line', - - init: function (el, values, options, width, height) { - line._super.init.call(this, el, values, options, width, height); - this.vertices = []; - this.regionMap = []; - this.xvalues = []; - this.yvalues = []; - this.yminmax = []; - this.hightlightSpotId = null; - this.lastShapeId = null; - this.initTarget(); - }, - - getRegion: function (el, x, y) { - var i, - regionMap = this.regionMap; // maps regions to value positions - for (i = regionMap.length; i--;) { - if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) { - return regionMap[i][2]; - } - } - return undefined; - }, - - getCurrentRegionFields: function () { - var currentRegion = this.currentRegion; - return { - isNull: this.yvalues[currentRegion] === null, - x: this.xvalues[currentRegion], - y: this.yvalues[currentRegion], - color: this.options.get('lineColor'), - fillColor: this.options.get('fillColor'), - offset: currentRegion - }; - }, - - renderHighlight: function () { - var currentRegion = this.currentRegion, - target = this.target, - vertex = this.vertices[currentRegion], - options = this.options, - spotRadius = options.get('spotRadius'), - highlightSpotColor = options.get('highlightSpotColor'), - highlightLineColor = options.get('highlightLineColor'), - highlightSpot, highlightLine; - - if (!vertex) { - return; - } - if (spotRadius && highlightSpotColor) { - highlightSpot = target.drawCircle(vertex[0], vertex[1], - spotRadius, undefined, highlightSpotColor); - this.highlightSpotId = highlightSpot.id; - target.insertAfterShape(this.lastShapeId, highlightSpot); - } - if (highlightLineColor) { - highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0], - this.canvasTop + this.canvasHeight, highlightLineColor); - this.highlightLineId = highlightLine.id; - target.insertAfterShape(this.lastShapeId, highlightLine); - } - }, - - removeHighlight: function () { - var target = this.target; - if (this.highlightSpotId) { - target.removeShapeId(this.highlightSpotId); - this.highlightSpotId = null; - } - if (this.highlightLineId) { - target.removeShapeId(this.highlightLineId); - this.highlightLineId = null; - } - }, - - scanValues: function () { - var values = this.values, - valcount = values.length, - xvalues = this.xvalues, - yvalues = this.yvalues, - yminmax = this.yminmax, - i, val, isStr, isArray, sp; - for (i = 0; i < valcount; i++) { - val = values[i]; - isStr = typeof(values[i]) === 'string'; - isArray = typeof(values[i]) === 'object' && values[i] instanceof Array; - sp = isStr && values[i].split(':'); - if (isStr && sp.length === 2) { // x:y - xvalues.push(Number(sp[0])); - yvalues.push(Number(sp[1])); - yminmax.push(Number(sp[1])); - } else if (isArray) { - xvalues.push(val[0]); - yvalues.push(val[1]); - yminmax.push(val[1]); - } else { - xvalues.push(i); - if (values[i] === null || values[i] === 'null') { - yvalues.push(null); - } else { - yvalues.push(Number(val)); - yminmax.push(Number(val)); - } - } - } - if (this.options.get('xvalues')) { - xvalues = this.options.get('xvalues'); - } - - this.maxy = this.maxyorg = Math.max.apply(Math, yminmax); - this.miny = this.minyorg = Math.min.apply(Math, yminmax); - - this.maxx = Math.max.apply(Math, xvalues); - this.minx = Math.min.apply(Math, xvalues); - - this.xvalues = xvalues; - this.yvalues = yvalues; - this.yminmax = yminmax; - - }, - - processRangeOptions: function () { - var options = this.options, - normalRangeMin = options.get('normalRangeMin'), - normalRangeMax = options.get('normalRangeMax'); - - if (normalRangeMin !== undefined) { - if (normalRangeMin < this.miny) { - this.miny = normalRangeMin; - } - if (normalRangeMax > this.maxy) { - this.maxy = normalRangeMax; - } - } - if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) { - this.miny = options.get('chartRangeMin'); - } - if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) { - this.maxy = options.get('chartRangeMax'); - } - if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) { - this.minx = options.get('chartRangeMinX'); - } - if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) { - this.maxx = options.get('chartRangeMaxX'); - } - - }, - - drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) { - var normalRangeMin = this.options.get('normalRangeMin'), - normalRangeMax = this.options.get('normalRangeMax'), - ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))), - height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey); - this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append(); - }, - - render: function () { - var options = this.options, - target = this.target, - canvasWidth = this.canvasWidth, - canvasHeight = this.canvasHeight, - vertices = this.vertices, - spotRadius = options.get('spotRadius'), - regionMap = this.regionMap, - rangex, rangey, yvallast, - canvasTop, canvasLeft, - vertex, path, paths, x, y, xnext, xpos, xposnext, - last, next, yvalcount, lineShapes, fillShapes, plen, - valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i; - - if (!line._super.render.call(this)) { - return; - } - - this.scanValues(); - this.processRangeOptions(); - - xvalues = this.xvalues; - yvalues = this.yvalues; - - if (!this.yminmax.length || this.yvalues.length < 2) { - // empty or all null valuess - return; - } - - canvasTop = canvasLeft = 0; - - rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx; - rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny; - yvallast = this.yvalues.length - 1; - - if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) { - spotRadius = 0; - } - if (spotRadius) { - // adjust the canvas size as required so that spots will fit - hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction'); - if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) { - canvasHeight -= Math.ceil(spotRadius); - } - if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) { - canvasHeight -= Math.ceil(spotRadius); - canvasTop += Math.ceil(spotRadius); - } - if (hlSpotsEnabled || - ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) { - canvasLeft += Math.ceil(spotRadius); - canvasWidth -= Math.ceil(spotRadius); - } - if (hlSpotsEnabled || options.get('spotColor') || - (options.get('minSpotColor') || options.get('maxSpotColor') && - (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) { - canvasWidth -= Math.ceil(spotRadius); - } - } - - - canvasHeight--; - - if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) { - this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey); - } - - path = []; - paths = [path]; - last = next = null; - yvalcount = yvalues.length; - for (i = 0; i < yvalcount; i++) { - x = xvalues[i]; - xnext = xvalues[i + 1]; - y = yvalues[i]; - xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)); - xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth; - next = xpos + ((xposnext - xpos) / 2); - regionMap[i] = [last || 0, next, i]; - last = next; - if (y === null) { - if (i) { - if (yvalues[i - 1] !== null) { - path = []; - paths.push(path); - } - vertices.push(null); - } - } else { - if (y < this.miny) { - y = this.miny; - } - if (y > this.maxy) { - y = this.maxy; - } - if (!path.length) { - // previous value was null - path.push([xpos, canvasTop + canvasHeight]); - } - vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))]; - path.push(vertex); - vertices.push(vertex); - } - } - - lineShapes = []; - fillShapes = []; - plen = paths.length; - for (i = 0; i < plen; i++) { - path = paths[i]; - if (path.length) { - if (options.get('fillColor')) { - path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]); - fillShapes.push(path.slice(0)); - path.pop(); - } - // if there's only a single point in this path, then we want to display it - // as a vertical line which means we keep path[0] as is - if (path.length > 2) { - // else we want the first value - path[0] = [path[0][0], path[1][1]]; - } - lineShapes.push(path); - } - } - - // draw the fill first, then optionally the normal range, then the line on top of that - plen = fillShapes.length; - for (i = 0; i < plen; i++) { - target.drawShape(fillShapes[i], - options.get('fillColor'), options.get('fillColor')).append(); - } - - if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) { - this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey); - } - - plen = lineShapes.length; - for (i = 0; i < plen; i++) { - target.drawShape(lineShapes[i], options.get('lineColor'), undefined, - options.get('lineWidth')).append(); - } - - if (spotRadius && options.get('valueSpots')) { - valueSpots = options.get('valueSpots'); - if (valueSpots.get === undefined) { - valueSpots = new RangeMap(valueSpots); - } - for (i = 0; i < yvalcount; i++) { - color = valueSpots.get(yvalues[i]); - if (color) { - target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))), - spotRadius, undefined, - color).append(); - } - } - - } - if (spotRadius && options.get('spotColor')) { - target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))), - spotRadius, undefined, - options.get('spotColor')).append(); - } - if (this.maxy !== this.minyorg) { - if (spotRadius && options.get('minSpotColor')) { - x = xvalues[$.inArray(this.minyorg, yvalues)]; - target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))), - spotRadius, undefined, - options.get('minSpotColor')).append(); - } - if (spotRadius && options.get('maxSpotColor')) { - x = xvalues[$.inArray(this.maxyorg, yvalues)]; - target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)), - canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))), - spotRadius, undefined, - options.get('maxSpotColor')).append(); - } - } - - this.lastShapeId = target.getLastShapeId(); - this.canvasTop = canvasTop; - target.render(); - } - }); - - /** - * Bar charts - */ - $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, { - type: 'bar', - - init: function (el, values, options, width, height) { - var barWidth = parseInt(options.get('barWidth'), 10), - barSpacing = parseInt(options.get('barSpacing'), 10), - chartRangeMin = options.get('chartRangeMin'), - chartRangeMax = options.get('chartRangeMax'), - chartRangeClip = options.get('chartRangeClip'), - stackMin = Infinity, - stackMax = -Infinity, - isStackString, groupMin, groupMax, stackRanges, - numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax, - stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf; - bar._super.init.call(this, el, values, options, width, height); - - // scan values to determine whether to stack bars - for (i = 0, vlen = values.length; i < vlen; i++) { - val = values[i]; - isStackString = typeof(val) === 'string' && val.indexOf(':') > -1; - if (isStackString || $.isArray(val)) { - stacked = true; - if (isStackString) { - val = values[i] = normalizeValues(val.split(':')); - } - val = remove(val, null); // min/max will treat null as zero - groupMin = Math.min.apply(Math, val); - groupMax = Math.max.apply(Math, val); - if (groupMin < stackMin) { - stackMin = groupMin; - } - if (groupMax > stackMax) { - stackMax = groupMax; - } - } - } - - this.stacked = stacked; - this.regionShapes = {}; - this.barWidth = barWidth; - this.barSpacing = barSpacing; - this.totalBarWidth = barWidth + barSpacing; - this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing); - - this.initTarget(); - - if (chartRangeClip) { - clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin; - clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax; - } - - numValues = []; - stackRanges = stacked ? [] : numValues; - var stackTotals = []; - var stackRangesNeg = []; - for (i = 0, vlen = values.length; i < vlen; i++) { - if (stacked) { - vlist = values[i]; - values[i] = svals = []; - stackTotals[i] = 0; - stackRanges[i] = stackRangesNeg[i] = 0; - for (j = 0, slen = vlist.length; j < slen; j++) { - val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j]; - if (val !== null) { - if (val > 0) { - stackTotals[i] += val; - } - if (stackMin < 0 && stackMax > 0) { - if (val < 0) { - stackRangesNeg[i] += Math.abs(val); - } else { - stackRanges[i] += val; - } - } else { - stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin)); - } - numValues.push(val); - } - } - } else { - val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i]; - val = values[i] = normalizeValue(val); - if (val !== null) { - numValues.push(val); - } - } - } - this.max = max = Math.max.apply(Math, numValues); - this.min = min = Math.min.apply(Math, numValues); - this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max; - this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min; - - if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) { - min = options.get('chartRangeMin'); - } - if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) { - max = options.get('chartRangeMax'); - } - - this.zeroAxis = zeroAxis = options.get('zeroAxis', true); - if (min <= 0 && max >= 0 && zeroAxis) { - xaxisOffset = 0; - } else if (zeroAxis == false) { - xaxisOffset = min; - } else if (min > 0) { - xaxisOffset = min; - } else { - xaxisOffset = max; - } - this.xaxisOffset = xaxisOffset; - - range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min; - - // as we plot zero/min values a single pixel line, we add a pixel to all other - // values - Reduce the effective canvas size to suit - this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1; - - if (min < xaxisOffset) { - yMaxCalc = (stacked && max >= 0) ? stackMax : max; - yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight; - if (yoffset !== Math.ceil(yoffset)) { - this.canvasHeightEf -= 2; - yoffset = Math.ceil(yoffset); - } - } else { - yoffset = this.canvasHeight; - } - this.yoffset = yoffset; - - if ($.isArray(options.get('colorMap'))) { - this.colorMapByIndex = options.get('colorMap'); - this.colorMapByValue = null; - } else { - this.colorMapByIndex = null; - this.colorMapByValue = options.get('colorMap'); - if (this.colorMapByValue && this.colorMapByValue.get === undefined) { - this.colorMapByValue = new RangeMap(this.colorMapByValue); - } - } - - this.range = range; - }, - - getRegion: function (el, x, y) { - var result = Math.floor(x / this.totalBarWidth); - return (result < 0 || result >= this.values.length) ? undefined : result; - }, - - getCurrentRegionFields: function () { - var currentRegion = this.currentRegion, - values = ensureArray(this.values[currentRegion]), - result = [], - value, i; - for (i = values.length; i--;) { - value = values[i]; - result.push({ - isNull: value === null, - value: value, - color: this.calcColor(i, value, currentRegion), - offset: currentRegion - }); - } - return result; - }, - - calcColor: function (stacknum, value, valuenum) { - var colorMapByIndex = this.colorMapByIndex, - colorMapByValue = this.colorMapByValue, - options = this.options, - color, newColor; - if (this.stacked) { - color = options.get('stackedBarColor'); - } else { - color = (value < 0) ? options.get('negBarColor') : options.get('barColor'); - } - if (value === 0 && options.get('zeroColor') !== undefined) { - color = options.get('zeroColor'); - } - if (colorMapByValue && (newColor = colorMapByValue.get(value))) { - color = newColor; - } else if (colorMapByIndex && colorMapByIndex.length > valuenum) { - color = colorMapByIndex[valuenum]; - } - return $.isArray(color) ? color[stacknum % color.length] : color; - }, - - /** - * Render bar(s) for a region - */ - renderRegion: function (valuenum, highlight) { - var vals = this.values[valuenum], - options = this.options, - xaxisOffset = this.xaxisOffset, - result = [], - range = this.range, - stacked = this.stacked, - target = this.target, - x = valuenum * this.totalBarWidth, - canvasHeightEf = this.canvasHeightEf, - yoffset = this.yoffset, - y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin; - - vals = $.isArray(vals) ? vals : [vals]; - valcount = vals.length; - val = vals[0]; - isNull = all(null, vals); - allMin = all(xaxisOffset, vals, true); - - if (isNull) { - if (options.get('nullColor')) { - color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options); - y = (yoffset > 0) ? yoffset - 1 : yoffset; - return target.drawRect(x, y, this.barWidth - 1, 0, color, color); - } else { - return undefined; - } - } - yoffsetNeg = yoffset; - for (i = 0; i < valcount; i++) { - val = vals[i]; - - if (stacked && val === xaxisOffset) { - if (!allMin || minPlotted) { - continue; - } - minPlotted = true; - } - - if (range > 0) { - height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1; - } else { - height = 1; - } - if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) { - y = yoffsetNeg; - yoffsetNeg += height; - } else { - y = yoffset - height; - yoffset -= height; - } - color = this.calcColor(i, val, valuenum); - if (highlight) { - color = this.calcHighlightColor(color, options); - } - result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color)); - } - if (result.length === 1) { - return result[0]; - } - return result; - } - }); - - /** - * Tristate charts - */ - $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, { - type: 'tristate', - - init: function (el, values, options, width, height) { - var barWidth = parseInt(options.get('barWidth'), 10), - barSpacing = parseInt(options.get('barSpacing'), 10); - tristate._super.init.call(this, el, values, options, width, height); - - this.regionShapes = {}; - this.barWidth = barWidth; - this.barSpacing = barSpacing; - this.totalBarWidth = barWidth + barSpacing; - this.values = $.map(values, Number); - this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing); - - if ($.isArray(options.get('colorMap'))) { - this.colorMapByIndex = options.get('colorMap'); - this.colorMapByValue = null; - } else { - this.colorMapByIndex = null; - this.colorMapByValue = options.get('colorMap'); - if (this.colorMapByValue && this.colorMapByValue.get === undefined) { - this.colorMapByValue = new RangeMap(this.colorMapByValue); - } - } - this.initTarget(); - }, - - getRegion: function (el, x, y) { - return Math.floor(x / this.totalBarWidth); - }, - - getCurrentRegionFields: function () { - var currentRegion = this.currentRegion; - return { - isNull: this.values[currentRegion] === undefined, - value: this.values[currentRegion], - color: this.calcColor(this.values[currentRegion], currentRegion), - offset: currentRegion - }; - }, - - calcColor: function (value, valuenum) { - var values = this.values, - options = this.options, - colorMapByIndex = this.colorMapByIndex, - colorMapByValue = this.colorMapByValue, - color, newColor; - - if (colorMapByValue && (newColor = colorMapByValue.get(value))) { - color = newColor; - } else if (colorMapByIndex && colorMapByIndex.length > valuenum) { - color = colorMapByIndex[valuenum]; - } else if (values[valuenum] < 0) { - color = options.get('negBarColor'); - } else if (values[valuenum] > 0) { - color = options.get('posBarColor'); - } else { - color = options.get('zeroBarColor'); - } - return color; - }, - - renderRegion: function (valuenum, highlight) { - var values = this.values, - options = this.options, - target = this.target, - canvasHeight, height, halfHeight, - x, y, color; - - canvasHeight = target.pixelHeight; - halfHeight = Math.round(canvasHeight / 2); - - x = valuenum * this.totalBarWidth; - if (values[valuenum] < 0) { - y = halfHeight; - height = halfHeight - 1; - } else if (values[valuenum] > 0) { - y = 0; - height = halfHeight - 1; - } else { - y = halfHeight - 1; - height = 2; - } - color = this.calcColor(values[valuenum], valuenum); - if (color === null) { - return; - } - if (highlight) { - color = this.calcHighlightColor(color, options); - } - return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color); - } - }); - - /** - * Discrete charts - */ - $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, { - type: 'discrete', - - init: function (el, values, options, width, height) { - discrete._super.init.call(this, el, values, options, width, height); - - this.regionShapes = {}; - this.values = values = $.map(values, Number); - this.min = Math.min.apply(Math, values); - this.max = Math.max.apply(Math, values); - this.range = this.max - this.min; - this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width; - this.interval = Math.floor(width / values.length); - this.itemWidth = width / values.length; - if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) { - this.min = options.get('chartRangeMin'); - } - if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) { - this.max = options.get('chartRangeMax'); - } - this.initTarget(); - if (this.target) { - this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight'); - } - }, - - getRegion: function (el, x, y) { - return Math.floor(x / this.itemWidth); - }, - - getCurrentRegionFields: function () { - var currentRegion = this.currentRegion; - return { - isNull: this.values[currentRegion] === undefined, - value: this.values[currentRegion], - offset: currentRegion - }; - }, - - renderRegion: function (valuenum, highlight) { - var values = this.values, - options = this.options, - min = this.min, - max = this.max, - range = this.range, - interval = this.interval, - target = this.target, - canvasHeight = this.canvasHeight, - lineHeight = this.lineHeight, - pheight = canvasHeight - lineHeight, - ytop, val, color, x; - - val = clipval(values[valuenum], min, max); - x = valuenum * interval; - ytop = Math.round(pheight - pheight * ((val - min) / range)); - color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor'); - if (highlight) { - color = this.calcHighlightColor(color, options); - } - return target.drawLine(x, ytop, x, ytop + lineHeight, color); - } - }); - - /** - * Bullet charts - */ - $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, { - type: 'bullet', - - init: function (el, values, options, width, height) { - var min, max, vals; - bullet._super.init.call(this, el, values, options, width, height); - - // values: target, performance, range1, range2, range3 - this.values = values = normalizeValues(values); - // target or performance could be null - vals = values.slice(); - vals[0] = vals[0] === null ? vals[2] : vals[0]; - vals[1] = values[1] === null ? vals[2] : vals[1]; - min = Math.min.apply(Math, values); - max = Math.max.apply(Math, values); - if (options.get('base') === undefined) { - min = min < 0 ? min : 0; - } else { - min = options.get('base'); - } - this.min = min; - this.max = max; - this.range = max - min; - this.shapes = {}; - this.valueShapes = {}; - this.regiondata = {}; - this.width = width = options.get('width') === 'auto' ? '4.0em' : width; - this.target = this.$el.simpledraw(width, height, options.get('composite')); - if (!values.length) { - this.disabled = true; - } - this.initTarget(); - }, - - getRegion: function (el, x, y) { - var shapeid = this.target.getShapeAt(el, x, y); - return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined; - }, - - getCurrentRegionFields: function () { - var currentRegion = this.currentRegion; - return { - fieldkey: currentRegion.substr(0, 1), - value: this.values[currentRegion.substr(1)], - region: currentRegion - }; - }, - - changeHighlight: function (highlight) { - var currentRegion = this.currentRegion, - shapeid = this.valueShapes[currentRegion], - shape; - delete this.shapes[shapeid]; - switch (currentRegion.substr(0, 1)) { - case 'r': - shape = this.renderRange(currentRegion.substr(1), highlight); - break; - case 'p': - shape = this.renderPerformance(highlight); - break; - case 't': - shape = this.renderTarget(highlight); - break; - } - this.valueShapes[currentRegion] = shape.id; - this.shapes[shape.id] = currentRegion; - this.target.replaceWithShape(shapeid, shape); - }, - - renderRange: function (rn, highlight) { - var rangeval = this.values[rn], - rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)), - color = this.options.get('rangeColors')[rn - 2]; - if (highlight) { - color = this.calcHighlightColor(color, this.options); - } - return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color); - }, - - renderPerformance: function (highlight) { - var perfval = this.values[1], - perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)), - color = this.options.get('performanceColor'); - if (highlight) { - color = this.calcHighlightColor(color, this.options); - } - return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1, - Math.round(this.canvasHeight * 0.4) - 1, color, color); - }, - - renderTarget: function (highlight) { - var targetval = this.values[0], - x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)), - targettop = Math.round(this.canvasHeight * 0.10), - targetheight = this.canvasHeight - (targettop * 2), - color = this.options.get('targetColor'); - if (highlight) { - color = this.calcHighlightColor(color, this.options); - } - return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color); - }, - - render: function () { - var vlen = this.values.length, - target = this.target, - i, shape; - if (!bullet._super.render.call(this)) { - return; - } - for (i = 2; i < vlen; i++) { - shape = this.renderRange(i).append(); - this.shapes[shape.id] = 'r' + i; - this.valueShapes['r' + i] = shape.id; - } - if (this.values[1] !== null) { - shape = this.renderPerformance().append(); - this.shapes[shape.id] = 'p1'; - this.valueShapes.p1 = shape.id; - } - if (this.values[0] !== null) { - shape = this.renderTarget().append(); - this.shapes[shape.id] = 't0'; - this.valueShapes.t0 = shape.id; - } - target.render(); - } - }); - - /** - * Pie charts - */ - $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, { - type: 'pie', - - init: function (el, values, options, width, height) { - var total = 0, i; - - pie._super.init.call(this, el, values, options, width, height); - - this.shapes = {}; // map shape ids to value offsets - this.valueShapes = {}; // maps value offsets to shape ids - this.values = values = $.map(values, Number); - - if (options.get('width') === 'auto') { - this.width = this.height; - } - - if (values.length > 0) { - for (i = values.length; i--;) { - total += values[i]; - } - } - this.total = total; - this.initTarget(); - this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2); - }, - - getRegion: function (el, x, y) { - var shapeid = this.target.getShapeAt(el, x, y); - return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined; - }, - - getCurrentRegionFields: function () { - var currentRegion = this.currentRegion; - return { - isNull: this.values[currentRegion] === undefined, - value: this.values[currentRegion], - percent: this.values[currentRegion] / this.total * 100, - color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length], - offset: currentRegion - }; - }, - - changeHighlight: function (highlight) { - var currentRegion = this.currentRegion, - newslice = this.renderSlice(currentRegion, highlight), - shapeid = this.valueShapes[currentRegion]; - delete this.shapes[shapeid]; - this.target.replaceWithShape(shapeid, newslice); - this.valueShapes[currentRegion] = newslice.id; - this.shapes[newslice.id] = currentRegion; - }, - - renderSlice: function (valuenum, highlight) { - var target = this.target, - options = this.options, - radius = this.radius, - borderWidth = options.get('borderWidth'), - offset = options.get('offset'), - circle = 2 * Math.PI, - values = this.values, - total = this.total, - next = offset ? (2*Math.PI)*(offset/360) : 0, - start, end, i, vlen, color; - - vlen = values.length; - for (i = 0; i < vlen; i++) { - start = next; - end = next; - if (total > 0) { // avoid divide by zero - end = next + (circle * (values[i] / total)); - } - if (valuenum === i) { - color = options.get('sliceColors')[i % options.get('sliceColors').length]; - if (highlight) { - color = this.calcHighlightColor(color, options); - } - - return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color); - } - next = end; - } - }, - - render: function () { - var target = this.target, - values = this.values, - options = this.options, - radius = this.radius, - borderWidth = options.get('borderWidth'), - shape, i; - - if (!pie._super.render.call(this)) { - return; - } - if (borderWidth) { - target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)), - options.get('borderColor'), undefined, borderWidth).append(); - } - for (i = values.length; i--;) { - if (values[i]) { // don't render zero values - shape = this.renderSlice(i).append(); - this.valueShapes[i] = shape.id; // store just the shapeid - this.shapes[shape.id] = i; - } - } - target.render(); - } - }); - - /** - * Box plots - */ - $.fn.sparkline.box = box = createClass($.fn.sparkline._base, { - type: 'box', - - init: function (el, values, options, width, height) { - box._super.init.call(this, el, values, options, width, height); - this.values = $.map(values, Number); - this.width = options.get('width') === 'auto' ? '4.0em' : width; - this.initTarget(); - if (!this.values.length) { - this.disabled = 1; - } - }, - - /** - * Simulate a single region - */ - getRegion: function () { - return 1; - }, - - getCurrentRegionFields: function () { - var result = [ - { field: 'lq', value: this.quartiles[0] }, - { field: 'med', value: this.quartiles[1] }, - { field: 'uq', value: this.quartiles[2] } - ]; - if (this.loutlier !== undefined) { - result.push({ field: 'lo', value: this.loutlier}); - } - if (this.routlier !== undefined) { - result.push({ field: 'ro', value: this.routlier}); - } - if (this.lwhisker !== undefined) { - result.push({ field: 'lw', value: this.lwhisker}); - } - if (this.rwhisker !== undefined) { - result.push({ field: 'rw', value: this.rwhisker}); - } - return result; - }, - - render: function () { - var target = this.target, - values = this.values, - vlen = values.length, - options = this.options, - canvasWidth = this.canvasWidth, - canvasHeight = this.canvasHeight, - minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'), - maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'), - canvasLeft = 0, - lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i, - size, unitSize; - - if (!box._super.render.call(this)) { - return; - } - - if (options.get('raw')) { - if (options.get('showOutliers') && values.length > 5) { - loutlier = values[0]; - lwhisker = values[1]; - q1 = values[2]; - q2 = values[3]; - q3 = values[4]; - rwhisker = values[5]; - routlier = values[6]; - } else { - lwhisker = values[0]; - q1 = values[1]; - q2 = values[2]; - q3 = values[3]; - rwhisker = values[4]; - } - } else { - values.sort(function (a, b) { return a - b; }); - q1 = quartile(values, 1); - q2 = quartile(values, 2); - q3 = quartile(values, 3); - iqr = q3 - q1; - if (options.get('showOutliers')) { - lwhisker = rwhisker = undefined; - for (i = 0; i < vlen; i++) { - if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) { - lwhisker = values[i]; - } - if (values[i] < q3 + (iqr * options.get('outlierIQR'))) { - rwhisker = values[i]; - } - } - loutlier = values[0]; - routlier = values[vlen - 1]; - } else { - lwhisker = values[0]; - rwhisker = values[vlen - 1]; - } - } - this.quartiles = [q1, q2, q3]; - this.lwhisker = lwhisker; - this.rwhisker = rwhisker; - this.loutlier = loutlier; - this.routlier = routlier; - - unitSize = canvasWidth / (maxValue - minValue + 1); - if (options.get('showOutliers')) { - canvasLeft = Math.ceil(options.get('spotRadius')); - canvasWidth -= 2 * Math.ceil(options.get('spotRadius')); - unitSize = canvasWidth / (maxValue - minValue + 1); - if (loutlier < lwhisker) { - target.drawCircle((loutlier - minValue) * unitSize + canvasLeft, - canvasHeight / 2, - options.get('spotRadius'), - options.get('outlierLineColor'), - options.get('outlierFillColor')).append(); - } - if (routlier > rwhisker) { - target.drawCircle((routlier - minValue) * unitSize + canvasLeft, - canvasHeight / 2, - options.get('spotRadius'), - options.get('outlierLineColor'), - options.get('outlierFillColor')).append(); - } - } - - // box - target.drawRect( - Math.round((q1 - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight * 0.1), - Math.round((q3 - q1) * unitSize), - Math.round(canvasHeight * 0.8), - options.get('boxLineColor'), - options.get('boxFillColor')).append(); - // left whisker - target.drawLine( - Math.round((lwhisker - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight / 2), - Math.round((q1 - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight / 2), - options.get('lineColor')).append(); - target.drawLine( - Math.round((lwhisker - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight / 4), - Math.round((lwhisker - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight - canvasHeight / 4), - options.get('whiskerColor')).append(); - // right whisker - target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight / 2), - Math.round((q3 - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight / 2), - options.get('lineColor')).append(); - target.drawLine( - Math.round((rwhisker - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight / 4), - Math.round((rwhisker - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight - canvasHeight / 4), - options.get('whiskerColor')).append(); - // median line - target.drawLine( - Math.round((q2 - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight * 0.1), - Math.round((q2 - minValue) * unitSize + canvasLeft), - Math.round(canvasHeight * 0.9), - options.get('medianColor')).append(); - if (options.get('target')) { - size = Math.ceil(options.get('spotRadius')); - target.drawLine( - Math.round((options.get('target') - minValue) * unitSize + canvasLeft), - Math.round((canvasHeight / 2) - size), - Math.round((options.get('target') - minValue) * unitSize + canvasLeft), - Math.round((canvasHeight / 2) + size), - options.get('targetColor')).append(); - target.drawLine( - Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size), - Math.round(canvasHeight / 2), - Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size), - Math.round(canvasHeight / 2), - options.get('targetColor')).append(); - } - target.render(); - } - }); - - // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier - // This is accessible as $(foo).simpledraw() - - if ($.browser.msie && document.namespaces && !document.namespaces.v) { - document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); - } - - if ($.browser.hasCanvas === undefined) { - $.browser.hasCanvas = document.createElement('canvas').getContext !== undefined; - } - - VShape = createClass({ - init: function (target, id, type, args) { - this.target = target; - this.id = id; - this.type = type; - this.args = args; - }, - append: function () { - this.target.appendShape(this); - return this; - } - }); - - VCanvas_base = createClass({ - _pxregex: /(\d+)(px)?\s*$/i, - - init: function (width, height, target) { - if (!width) { - return; - } - this.width = width; - this.height = height; - this.target = target; - this.lastShapeId = null; - if (target[0]) { - target = target[0]; - } - $.data(target, '_jqs_vcanvas', this); - }, - - drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) { - return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth); - }, - - drawShape: function (path, lineColor, fillColor, lineWidth) { - return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]); - }, - - drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) { - return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]); - }, - - drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) { - return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]); - }, - - drawRect: function (x, y, width, height, lineColor, fillColor) { - return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]); - }, - - getElement: function () { - return this.canvas; - }, - - /** - * Return the most recently inserted shape id - */ - getLastShapeId: function () { - return this.lastShapeId; - }, - - /** - * Clear and reset the canvas - */ - reset: function () { - alert('reset not implemented'); - }, - - _insert: function (el, target) { - $(target).html(el); - }, - - /** - * Calculate the pixel dimensions of the canvas - */ - _calculatePixelDims: function (width, height, canvas) { - // XXX This should probably be a configurable option - var match; - match = this._pxregex.exec(height); - if (match) { - this.pixelHeight = match[1]; - } else { - this.pixelHeight = $(canvas).height(); - } - match = this._pxregex.exec(width); - if (match) { - this.pixelWidth = match[1]; - } else { - this.pixelWidth = $(canvas).width(); - } - }, - - /** - * Generate a shape object and id for later rendering - */ - _genShape: function (shapetype, shapeargs) { - var id = shapeCount++; - shapeargs.unshift(id); - return new VShape(this, id, shapetype, shapeargs); - }, - - /** - * Add a shape to the end of the render queue - */ - appendShape: function (shape) { - alert('appendShape not implemented'); - }, - - /** - * Replace one shape with another - */ - replaceWithShape: function (shapeid, shape) { - alert('replaceWithShape not implemented'); - }, - - /** - * Insert one shape after another in the render queue - */ - insertAfterShape: function (shapeid, shape) { - alert('insertAfterShape not implemented'); - }, - - /** - * Remove a shape from the queue - */ - removeShapeId: function (shapeid) { - alert('removeShapeId not implemented'); - }, - - /** - * Find a shape at the specified x/y co-ordinates - */ - getShapeAt: function (el, x, y) { - alert('getShapeAt not implemented'); - }, - - /** - * Render all queued shapes onto the canvas - */ - render: function () { - alert('render not implemented'); - } - }); - - VCanvas_canvas = createClass(VCanvas_base, { - init: function (width, height, target, interact) { - VCanvas_canvas._super.init.call(this, width, height, target); - this.canvas = document.createElement('canvas'); - if (target[0]) { - target = target[0]; - } - $.data(target, '_jqs_vcanvas', this); - $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' }); - this._insert(this.canvas, target); - this._calculatePixelDims(width, height, this.canvas); - this.canvas.width = this.pixelWidth; - this.canvas.height = this.pixelHeight; - this.interact = interact; - this.shapes = {}; - this.shapeseq = []; - this.currentTargetShapeId = undefined; - $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight}); - }, - - _getContext: function (lineColor, fillColor, lineWidth) { - var context = this.canvas.getContext('2d'); - if (lineColor !== undefined) { - context.strokeStyle = lineColor; - } - context.lineWidth = lineWidth === undefined ? 1 : lineWidth; - if (fillColor !== undefined) { - context.fillStyle = fillColor; - } - return context; - }, - - reset: function () { - var context = this._getContext(); - context.clearRect(0, 0, this.pixelWidth, this.pixelHeight); - this.shapes = {}; - this.shapeseq = []; - this.currentTargetShapeId = undefined; - }, - - _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) { - var context = this._getContext(lineColor, fillColor, lineWidth), - i, plen; - context.beginPath(); - context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5); - for (i = 1, plen = path.length; i < plen; i++) { - context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines - } - if (lineColor !== undefined) { - context.stroke(); - } - if (fillColor !== undefined) { - context.fill(); - } - if (this.targetX !== undefined && this.targetY !== undefined && - context.isPointInPath(this.targetX, this.targetY)) { - this.currentTargetShapeId = shapeid; - } - }, - - _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) { - var context = this._getContext(lineColor, fillColor, lineWidth); - context.beginPath(); - context.arc(x, y, radius, 0, 2 * Math.PI, false); - if (this.targetX !== undefined && this.targetY !== undefined && - context.isPointInPath(this.targetX, this.targetY)) { - this.currentTargetShapeId = shapeid; - } - if (lineColor !== undefined) { - context.stroke(); - } - if (fillColor !== undefined) { - context.fill(); - } - }, - - _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) { - var context = this._getContext(lineColor, fillColor); - context.beginPath(); - context.moveTo(x, y); - context.arc(x, y, radius, startAngle, endAngle, false); - context.lineTo(x, y); - context.closePath(); - if (lineColor !== undefined) { - context.stroke(); - } - if (fillColor) { - context.fill(); - } - if (this.targetX !== undefined && this.targetY !== undefined && - context.isPointInPath(this.targetX, this.targetY)) { - this.currentTargetShapeId = shapeid; - } - }, - - _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) { - return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor); - }, - - appendShape: function (shape) { - this.shapes[shape.id] = shape; - this.shapeseq.push(shape.id); - this.lastShapeId = shape.id; - return shape.id; - }, - - replaceWithShape: function (shapeid, shape) { - var shapeseq = this.shapeseq, - i; - this.shapes[shape.id] = shape; - for (i = shapeseq.length; i--;) { - if (shapeseq[i] == shapeid) { - shapeseq[i] = shape.id; - } - } - delete this.shapes[shapeid]; - }, - - replaceWithShapes: function (shapeids, shapes) { - var shapeseq = this.shapeseq, - shapemap = {}, - sid, i, first; - - for (i = shapeids.length; i--;) { - shapemap[shapeids[i]] = true; - } - for (i = shapeseq.length; i--;) { - sid = shapeseq[i]; - if (shapemap[sid]) { - shapeseq.splice(i, 1); - delete this.shapes[sid]; - first = i; - } - } - for (i = shapes.length; i--;) { - shapeseq.splice(first, 0, shapes[i].id); - this.shapes[shapes[i].id] = shapes[i]; - } - - }, - - insertAfterShape: function (shapeid, shape) { - var shapeseq = this.shapeseq, - i; - for (i = shapeseq.length; i--;) { - if (shapeseq[i] === shapeid) { - shapeseq.splice(i + 1, 0, shape.id); - this.shapes[shape.id] = shape; - return; - } - } - }, - - removeShapeId: function (shapeid) { - var shapeseq = this.shapeseq, - i; - for (i = shapeseq.length; i--;) { - if (shapeseq[i] === shapeid) { - shapeseq.splice(i, 1); - break; - } - } - delete this.shapes[shapeid]; - }, - - getShapeAt: function (el, x, y) { - this.targetX = x; - this.targetY = y; - this.render(); - return this.currentTargetShapeId; - }, - - render: function () { - var shapeseq = this.shapeseq, - shapes = this.shapes, - shapeCount = shapeseq.length, - context = this._getContext(), - shapeid, shape, i; - context.clearRect(0, 0, this.pixelWidth, this.pixelHeight); - for (i = 0; i < shapeCount; i++) { - shapeid = shapeseq[i]; - shape = shapes[shapeid]; - this['_draw' + shape.type].apply(this, shape.args); - } - if (!this.interact) { - // not interactive so no need to keep the shapes array - this.shapes = {}; - this.shapeseq = []; - } - } - - }); - - VCanvas_vml = createClass(VCanvas_base, { - init: function (width, height, target) { - var groupel; - VCanvas_vml._super.init.call(this, width, height, target); - if (target[0]) { - target = target[0]; - } - $.data(target, '_jqs_vcanvas', this); - this.canvas = document.createElement('span'); - $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'}); - this._insert(this.canvas, target); - this._calculatePixelDims(width, height, this.canvas); - this.canvas.width = this.pixelWidth; - this.canvas.height = this.pixelHeight; - groupel = ''; - this.canvas.insertAdjacentHTML('beforeEnd', groupel); - this.group = $(this.canvas).children()[0]; - this.rendered = false; - this.prerender = ''; - }, - - _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) { - var vpath = [], - initial, stroke, fill, closed, vel, plen, i; - for (i = 0, plen = path.length; i < plen; i++) { - vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]); - } - initial = vpath.splice(0, 1); - lineWidth = lineWidth === undefined ? 1 : lineWidth; - stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" '; - fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; - closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : ''; - vel = '' + - ' '; - return vel; - }, - - _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) { - var stroke, fill, vel; - x -= radius; - y -= radius; - stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" '; - fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; - vel = ''; - return vel; - - }, - - _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) { - var vpath, startx, starty, endx, endy, stroke, fill, vel; - if (startAngle === endAngle) { - return; // VML seems to have problem when start angle equals end angle. - } - if ((endAngle - startAngle) === (2 * Math.PI)) { - startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0 - endAngle = (2 * Math.PI); - } - - startx = x + Math.round(Math.cos(startAngle) * radius); - starty = y + Math.round(Math.sin(startAngle) * radius); - endx = x + Math.round(Math.cos(endAngle) * radius); - endy = y + Math.round(Math.sin(endAngle) * radius); - - // Prevent very small slices from being mistaken as a whole pie - if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) { - return; - } - - vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy]; - stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" '; - fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; - vel = '' + - ' '; - return vel; - }, - - _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) { - return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor); - }, - - reset: function () { - this.group.innerHTML = ''; - }, - - appendShape: function (shape) { - var vel = this['_draw' + shape.type].apply(this, shape.args); - if (this.rendered) { - this.group.insertAdjacentHTML('beforeEnd', vel); - } else { - this.prerender += vel; - } - this.lastShapeId = shape.id; - return shape.id; - }, - - replaceWithShape: function (shapeid, shape) { - var existing = $('#jqsshape' + shapeid), - vel = this['_draw' + shape.type].apply(this, shape.args); - existing[0].outerHTML = vel; - }, - - replaceWithShapes: function (shapeids, shapes) { - // replace the first shapeid with all the new shapes then toast the remaining old shapes - var existing = $('#jqsshape' + shapeids[0]), - replace = '', - slen = shapes.length, - i; - for (i = 0; i < slen; i++) { - replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args); - } - existing[0].outerHTML = replace; - for (i = 1; i < shapeids.length; i++) { - $('#jqsshape' + shapeids[i]).remove(); - } - }, - - insertAfterShape: function (shapeid, shape) { - var existing = $('#jqsshape' + shapeid), - vel = this['_draw' + shape.type].apply(this, shape.args); - existing[0].insertAdjacentHTML('afterEnd', vel); - }, - - removeShapeId: function (shapeid) { - var existing = $('#jqsshape' + shapeid); - this.group.removeChild(existing[0]); - }, - - getShapeAt: function (el, x, y) { - var shapeid = el.id.substr(8); - return shapeid; - }, - - render: function () { - if (!this.rendered) { - // batch the intial render into a single repaint - this.group.innerHTML = this.prerender; - this.rendered = true; - } - } - }); - -})); diff --git a/solr/webapp/web/js/main.js b/solr/webapp/web/js/main.js index b66bc587768..635f15c733a 100644 --- a/solr/webapp/web/js/main.js +++ b/solr/webapp/web/js/main.js @@ -25,7 +25,6 @@ require 'lib/order!lib/jquery.form', 'lib/order!lib/jquery.jstree', 'lib/order!lib/jquery.sammy', - 'lib/order!lib/jquery.sparkline', 'lib/order!lib/jquery.timeago', 'lib/order!lib/jquery.blockUI', 'lib/order!lib/highlight', diff --git a/solr/webapp/web/js/scripts/app.js b/solr/webapp/web/js/scripts/app.js index d4dccd20ec6..78842778780 100644 --- a/solr/webapp/web/js/scripts/app.js +++ b/solr/webapp/web/js/scripts/app.js @@ -468,6 +468,11 @@ var solr_admin = function( app_config ) return json_str; }; + this.format_number = function format_number( number ) + { + return ( number || 0 ).toString().replace( /\B(?=(\d{3})+(?!\d))/g, ' ' ); + }; + }; var app = new solr_admin( app_config ); diff --git a/solr/webapp/web/js/scripts/dataimport.js b/solr/webapp/web/js/scripts/dataimport.js index e70780a2ed0..1b2b7f1a11f 100644 --- a/solr/webapp/web/js/scripts/dataimport.js +++ b/solr/webapp/web/js/scripts/dataimport.js @@ -440,11 +440,6 @@ sammy.get var messages_count = 0; for( var key in messages ) { messages_count++; } - var format_number = function format_number( number ) - { - return ( number || 0 ).toString().replace( /\B(?=(\d{3})+(?!\d))/g, '\'' ); - }; - function dataimport_compute_details( response, details_element, elapsed_seconds ) { details_element @@ -464,10 +459,10 @@ sammy.get { var value = parseInt( response.statusMessages[document_config[key]], 10 ); - var detail = '' + key.esc() + ': ' + format_number( value ).esc(); + var detail = '' + key.esc() + ': ' + app.format_number( value ).esc(); if( elapsed_seconds && 'skipped' !== key.toLowerCase() ) { - detail += ' (' + format_number( Math.round( value / elapsed_seconds ) ).esc() + '/s)' + detail += ' (' + app.format_number( Math.round( value / elapsed_seconds ) ).esc() + '/s)' } document_details.push( detail ); @@ -523,7 +518,7 @@ sammy.get /\d{4,}/g, function( match, position, string ) { - return format_number( parseInt( match, 10 ) ); + return app.format_number( parseInt( match, 10 ) ); } ); diff --git a/solr/webapp/web/js/scripts/schema-browser.js b/solr/webapp/web/js/scripts/schema-browser.js index bf2adbaf1be..58a5ce8b0a4 100644 --- a/solr/webapp/web/js/scripts/schema-browser.js +++ b/solr/webapp/web/js/scripts/schema-browser.js @@ -66,9 +66,7 @@ var load_terminfo = function( trigger_element, core_basepath, field, data_elemen }, success : function( response, text_status, xhr ) { - $( 'span', trigger_element ) - .removeClass( 'loader' ); - + var additional_styles = []; var field_data = response.fields[field]; if( !field_data || !( field_data.topTerms && field_data.histogram ) ) @@ -79,6 +77,11 @@ var load_terminfo = function( trigger_element, core_basepath, field, data_elemen return false; } + var get_width = function get_width() + { + return $( this ).width(); + } + var topterms_holder_element = $( '.topterms-holder', data_element ); var histogram_holder_element = $( '.histogram-holder', data_element ); @@ -111,7 +114,7 @@ var load_terminfo = function( trigger_element, core_basepath, field, data_elemen topterms_frq_last = topterms[i+1]; topterms_content += '
  • ' - + '

    ' + topterms_frq_last.esc() + '

    ' + "\n" + + '

    ' + app.format_number( topterms_frq_last ) + '

    ' + "\n" + '
      ' + "\n"; } @@ -129,6 +132,13 @@ var load_terminfo = function( trigger_element, core_basepath, field, data_elemen topterms_table_element .html( topterms_content ); + var max_width = 10 + Math.max.apply( Math, $( 'p', topterms_table_element ).map( get_width ).get() ); + additional_styles.push + ( + topterms_table_element.selector + ' p { width: ' + max_width + 'px !important; }' + "\n" + + topterms_table_element.selector + ' ul { margin-left: ' + ( max_width + 5 ) + 'px !important; }' + ); + topterms_count_element .val( topterms_count ); @@ -152,52 +162,57 @@ var load_terminfo = function( trigger_element, core_basepath, field, data_elemen histogram_holder_element .show(); - var histogram_element = $( '.histogram', histogram_holder_element ); - var histogram_values = luke_array_to_hash( field_data.histogram ); - var histogram_legend = ''; - - histogram_holder_element - .show(); + var histogram_entries = []; + + var histogram_max = null; + for( var key in histogram_values ) + { + histogram_max = Math.max( histogram_max, histogram_values[key] ); + } for( var key in histogram_values ) { - histogram_legend += '
      ' + key + '
      ' + "\n" + - '
      ' + - '' + histogram_values[key] + '' + - '
      ' + "\n"; + histogram_entries.push + ( + '
    • ' + "\n" + + '
      ' + "\n" + + '
      ' + app.format_number( key ) + '
      ' + "\n" + + '
      ' + app.format_number( histogram_values[key] ) + '
      ' + "\n" + + '
      ' + "\n" + + '
    • ' + ); } - $( 'dl', histogram_holder_element ) - .html( histogram_legend ); + $( 'ul', histogram_holder_element ) + .html( histogram_entries.join( "\n" ) ); - var histogram_values = luke_array_to_struct( field_data.histogram ).values; + $( 'ul li:even', histogram_holder_element ) + .addClass( 'odd' ); - histogram_element - .sparkline - ( - histogram_values, - { - type : 'bar', - barColor : '#c0c0c0', - zeroColor : '#000000', - height : histogram_element.height(), - barWidth : 46, - barSpacing : 3 - } - ); - - 1 === histogram_values.length - ? histogram_element.addClass( 'single' ) - : histogram_element.removeClass( 'single' ); + var max_width = 10 + Math.max.apply( Math, $( 'dt', histogram_holder_element ).map( get_width ).get() ); + additional_styles.push + ( + histogram_holder_element.selector + ' ul { margin-left: ' + max_width + 'px !important; }' + "\n" + + histogram_holder_element.selector + ' li dt { left: ' + ( max_width * -1 ) + 'px !important; width: ' + max_width + 'px !important; }' + ); } + if( additional_styles ) + { + terminfo_element + .prepend( '' ); + } }, error : function( xhr, text_status, error_thrown) { + terminfo_element + .addClass( 'disabled' ); }, complete : function( xhr, text_status ) { + $( 'span', trigger_element ) + .removeClass( 'loader' ); } } ); diff --git a/solr/webapp/web/tpl/schema-browser.html b/solr/webapp/web/tpl/schema-browser.html index 46eccc3ff19..391c20649f5 100644 --- a/solr/webapp/web/tpl/schema-browser.html +++ b/solr/webapp/web/tpl/schema-browser.html @@ -158,12 +158,8 @@ limitations under the License.

      Histogram:

      -
      +
        -
        - -
        -