1 // Simple test that we can use xapian from java
2 //
3 // Copyright (C) 2005,2006,2007,2008,2011,2016,2017,2019 Olly Betts
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
18 // USA
19 
20 import org.xapian.*;
21 
22 // FIXME: need to sort out throwing wrapped Xapian::Error subclasses
23 //import org.xapian.errors.*;
24 
25 // FIXME: "implements" not "extends" in JNI Java API
26 class MyMatchDecider extends MatchDecider {
accept(Document d)27     public boolean accept(Document d) {
28 	// NB It's not normally appropriate to call getData() in a MatchDecider
29 	// but we do it here to make sure we don't get an empty document.
30 /*	try { */
31 	    return d.getData().length() == 0;
32 /*
33 	} catch (XapianError e) {
34 	    return true;
35 	}
36 */
37     }
38 }
39 
40 // FIXME: "implements" not "extends" in JNI Java API
41 class MyExpandDecider extends ExpandDecider {
accept(String s)42     public boolean accept(String s) { return s.charAt(0) != 'a'; }
43 }
44 
45 class MyFieldProcessor extends FieldProcessor {
apply(String str)46     public Query apply(String str) {
47 	if (str.equals("spam"))
48 	    return new Query("eggs");
49 	return new Query("spam");
50     }
51 }
52 
53 public class SmokeTest {
main(String[] args)54     public static void main(String[] args) throws Exception {
55 	TermGenerator termGenerator = new TermGenerator();
56 	termGenerator.setFlags(TermGenerator.FLAG_SPELLING);
57 	try {
58 	    // Test the version number reporting functions give plausible
59 	    // results.
60 	    String v = "";
61 	    v += Version.major();
62 	    v += ".";
63 	    v += Version.minor();
64 	    v += ".";
65 	    v += Version.revision();
66 	    String v2 = Version.string();
67 	    if (!v.equals(v2)) {
68 		System.err.println("Unexpected version output (" + v + " != " + v2 + ")");
69 		System.exit(1);
70 	    }
71 
72 	    Stem stem = new Stem("english");
73 	    if (!stem.toString().equals("Xapian::Stem(english)")) {
74 		System.err.println("Unexpected stem.toString()");
75 		System.exit(1);
76 	    }
77 	    Document doc = new Document();
78 	    doc.setData("a\000b");
79 	    String s = doc.getData();
80 	    if (s.equals("a")) {
81 		System.err.println("getData+setData truncates at a zero byte");
82 		System.exit(1);
83 	    }
84 	    if (!s.equals("a\000b")) {
85 		System.err.println("getData+setData doesn't transparently handle a zero byte");
86 		System.exit(1);
87 	    }
88 	    doc.setData("is there anybody out there?");
89 	    doc.addTerm("XYzzy");
90 // apply was stemWord() in the JNI bindings
91 	    doc.addPosting(stem.apply("is"), 1);
92 	    doc.addPosting(stem.apply("there"), 2);
93 	    doc.addPosting(stem.apply("anybody"), 3);
94 	    doc.addPosting(stem.apply("out"), 4);
95 	    doc.addPosting(stem.apply("there"), 5);
96 	    WritableDatabase db = new WritableDatabase("", Xapian.DB_BACKEND_INMEMORY);
97 	    db.addDocument(doc);
98 	    if (db.getDocCount() != 1) {
99 		System.err.println("Unexpected db.getDocCount()");
100 		System.exit(1);
101 	    }
102 
103 	    QueryParser qp = new QueryParser();
104 
105 	    // Test wrapping of null-able grouping parameter.
106 	    qp.addBooleanPrefix("colour", "XC");
107 	    qp.addBooleanPrefix("color", "XC");
108 	    qp.addBooleanPrefix("foo", "XFOO", null);
109 	    qp.addBooleanPrefix("bar", "XBAR", "XBA*");
110 	    qp.addBooleanPrefix("baa", "XBAA", "XBA*");
111 	    DateRangeProcessor rpdate = new DateRangeProcessor(1, Xapian.RP_DATE_PREFER_MDY, 1960);
112 	    qp.addRangeprocessor(rpdate);
113 	    qp.addRangeprocessor(rpdate, null);
114 	    qp.addRangeprocessor(rpdate, "foo");
115 
116             if (!Query.MatchAll.toString().equals("Query(<alldocuments>)")) {
117 		System.err.println("Unexpected Query.MatchAll.toString()");
118 		System.exit(1);
119             }
120 
121             if (!Query.MatchNothing.toString().equals("Query()")) {
122 		System.err.println("Unexpected Query.MatchNothing.toString()");
123 		System.exit(1);
124             }
125 
126 	    String[] terms = { "smoke", "test", "terms" };
127 	    Query query = new Query(Query.OP_OR, terms);
128 	    if (!query.toString().equals("Query((smoke OR test OR terms))")) {
129 		System.err.println("Unexpected query.toString()");
130 		System.exit(1);
131 	    }
132 	    Query[] queries = { new Query("smoke"), query, new Query("string") };
133 	    Query query2 = new Query(Query.OP_XOR, queries);
134 	    if (!query2.toString().equals("Query((smoke XOR (smoke OR test OR terms) XOR string))")) {
135 		System.err.println("Unexpected query2.toString()");
136 		System.exit(1);
137 	    }
138 	    String[] subqs = { "a", "b" };
139 	    Query query3 = new Query(Query.OP_OR, subqs);
140 	    if (!query3.toString().equals("Query((a OR b))")) {
141 		System.err.println("Unexpected query3.toString()");
142 		System.exit(1);
143 	    }
144 	    Enquire enq = new Enquire(db);
145 
146 	    // Check Xapian::BAD_VALUENO is wrapped suitably.
147 	    enq.setCollapseKey(Xapian.BAD_VALUENO);
148 
149 	    // Test that the non-constant wrapping prior to 1.4.10 still works.
150 	    enq.setCollapseKey(Xapian.getBAD_VALUENO());
151 
152 	    enq.setQuery(new Query(Query.OP_OR, "there", "is"));
153 	    MSet mset = enq.getMSet(0, 10);
154 	    if (mset.size() != 1) {
155 		System.err.println("Unexpected mset.size()");
156 		System.exit(1);
157 	    }
158 	    MSetIterator m_itor = mset.begin();
159 	    Document m_doc = null;
160 	    long m_id;
161 	    while(m_itor.hasNext()) {
162 		m_id = m_itor.next();
163 		if(m_itor.hasNext()) {
164 		    m_doc = mset.getDocument(m_id);
165 		}
166 	    }
167 
168 	    // Only one doc exists in this mset
169 	    if(m_doc != null && m_doc.getDocId() != 0) {
170 		System.err.println("Unexpected docid");
171 		    System.exit(1);
172 	    }
173 
174 	    String term_str = "";
175 	    TermIterator itor = enq.getMatchingTermsBegin(mset.getElement(0));
176 	    while (itor.hasNext()) {
177 		term_str += itor.next();
178 		if (itor.hasNext())
179 		    term_str += ' ';
180 	    }
181 	    if (!term_str.equals("is there")) {
182 		System.err.println("Unexpected term_str");
183 		System.exit(1);
184 	    }
185 /* FIXME:dc: Fails since Xapian::Error is still unmapped
186 	    boolean ok = false;
187 	    try {
188 		Database db_fail = new Database("NOsuChdaTabASe");
189 		// Ignore the return value.
190 		db_fail.getDocCount();
191 	    } catch (DatabaseOpeningError e) {
192 		ok = true;
193 	    }
194 	    if (!ok) {
195 		System.err.println("Managed to open non-existent database");
196 		System.exit(1);
197 	    }
198 */
199 /*
200 	    if (Query.OP_ELITE_SET != 10) {
201 		System.err.println("OP_ELITE_SET is " + Query.OP_ELITE_SET + " not 10");
202 		System.exit(1);
203 	    }
204 */
205 	    RSet rset = new RSet();
206 	    rset.addDocument(1);
207 	    ESet eset = enq.getESet(10, rset, new MyExpandDecider());
208 	    // FIXME: temporary simple check
209 	    if (0 == eset.size()) {
210 		System.err.println("ESet.size() was 0");
211 		System.exit(1);
212 	    }
213 
214 	    int count = 0;
215 	    for(ESetIterator eit = eset.begin(); eit.hasNext(); ) {
216 	    // for (int i = 0; i < eset.size(); i++) {
217 		if (eit.getTerm().charAt(0) == 'a') {
218 		    System.err.println("MyExpandDecider wasn't used");
219 		    System.exit(1);
220 		}
221 		++count;
222 		eit.next();
223 	    }
224 	    if (count != eset.size()) {
225 		System.err.println("ESet.size() mismatched number of terms returned by ESetIterator");
226 		System.err.println(count + " " + eset.size());
227 		System.exit(1);
228 	    }
229 
230 /*
231 	    MSet mset2 = enq.getMSet(0, 10, null, new MyMatchDecider());
232 	    if (mset2.size() > 0) {
233 		System.err.println("MyMatchDecider wasn't used");
234 		System.exit(1);
235 	    }
236 */
237 
238 	    if (!enq.getQuery().toString().equals("Query((there OR is))")) {
239 		System.err.println("Enquire::getQuery() returned the wrong query: " + enq.getQuery().toString());
240 		System.exit(1);
241 	    }
242 
243 	    {
244 		qp.addPrefix("food", new MyFieldProcessor());
245 		if (!qp.parseQuery("food:spam").toString().equals("Query(eggs)")) {
246 		    System.err.println("FieldProcessor subclass doesn't work as expected");
247 		    System.exit(1);
248 		}
249 	    }
250 
251 	    {
252 		// Wrapped functions which take/return byte[] for std::string.
253 
254 		// Check that serialisation returns byte[], that
255 		// unserialisation takes byte[], and round-tripping works.
256 		byte[] res = Xapian.sortableSerialise(1.675);
257 		if (Xapian.sortableUnserialise(res) != 1.675) {
258 		    System.err.println("sortableSerialise() and/or sortableUnserialise() don't work as expected");
259 		    System.exit(1);
260 		}
261 
262 		// Check that serialisation returns byte[], that
263 		// unserialisation takes byte[], and round-tripping works.
264 		Query q = new Query("foo");
265 		res = q.serialise();
266 		Query q_out = Query.unserialise(res);
267 		if (!q.toString().equals(q_out.toString())) {
268 		    System.err.println("Query serialisation doesn't work as expected");
269 		    System.exit(1);
270 		}
271 
272 		// Check Document.addValue() takes byte[], that getValue()
273 		// returns byte[], that serialisation returns byte[], that
274 		// unserialisation takes byte[], and round-tripping works.
275 		Document d = new Document();
276 		d.setData("xyzzy");
277 		d.addValue(7, res);
278 		byte[] res2 = d.getValue(7);
279 		if (!java.util.Arrays.equals(res, res2)) {
280 		    System.err.println("Document.getValue() returns a different byte[] to the one set with addValue()");
281 		    System.exit(1);
282 		}
283 		res = d.serialise();
284 		Document d_out = Document.unserialise(res);
285 		// Make sure the "terms_here" flag is set so the descriptions match.
286 		d_out.termListCount();
287 		if (!d.toString().equals(d_out.toString())) {
288 		    System.err.println("Document serialisation doesn't work as expected");
289 		    System.err.println(d.toString());
290 		    System.err.println(d_out.toString());
291 		    System.exit(1);
292 		}
293 
294 		// Check that serialisation returns byte[], that
295 		// unserialisation takes byte[], and round-tripping works.
296 		LatLongCoord llc = new LatLongCoord(10.5, 45.25);
297 		res = llc.serialise();
298 		LatLongCoord llc_out = new LatLongCoord();
299 		llc_out.unserialise(res);
300 		if (!llc.toString().equals(llc_out.toString())) {
301 		    System.err.println("LatLongCoord serialisation doesn't work as expected");
302 		    System.exit(1);
303 		}
304 
305 		// Check that serialisation returns byte[], that
306 		// unserialisation takes byte[], and round-tripping works.
307 		LatLongCoords llcs = new LatLongCoords();
308 		llcs.append(llc);
309 		res = llcs.serialise();
310 		LatLongCoords llcs_out = new LatLongCoords();
311 		llcs_out.unserialise(res);
312 		if (!llcs.toString().equals(llcs_out.toString())) {
313 		    System.err.println("LatLongCoords serialisation doesn't work as expected");
314 		    System.exit(1);
315 		}
316 
317 		// Check `range_limit` mapped to byte[].
318 		q = new Query(Query.op.OP_VALUE_GE, 0, res);
319 		if (q.toString().length() == 0) {
320 		    // Mostly just a way to actually use the constructed object.
321 		    System.err.println("Query description shouldn't be empty");
322 		    System.exit(1);
323 		}
324 
325 		// Check `range_limit` mapped to byte[].
326 		q = new Query(Query.op.OP_VALUE_LE, 1, res);
327 		if (q.toString().length() == 0) {
328 		    // Mostly just a way to actually use the constructed object.
329 		    System.err.println("Query description shouldn't be empty");
330 		    System.exit(1);
331 		}
332 
333 		// Check `range_lower` and `range_upper` mapped to byte[].
334 		q = new Query(Query.op.OP_VALUE_RANGE, 2, res, res);
335 		if (q.toString().length() == 0) {
336 		    // Mostly just a way to actually use the constructed object.
337 		    System.err.println("Query description shouldn't be empty");
338 		    System.exit(1);
339 		}
340 
341 		// Check ValueSetMatchDecider.addValue() and removeValue() take
342 		// byte[].
343 		ValueSetMatchDecider vsmd = new ValueSetMatchDecider(1, false);
344 		vsmd.addValue(res);
345 		vsmd.removeValue(res);
346 
347 		// Check Database.getValueLowerBound() and getValueUpperBound()
348 		// return byte[].
349 		byte[] lo = "abba".getBytes();
350 		byte[] hi = "xyzzy".getBytes();
351 		{
352 		    WritableDatabase wdb = new WritableDatabase("", Xapian.DB_BACKEND_INMEMORY);
353 		    Document document = new Document();
354 		    document.addValue(42, hi);
355 		    wdb.addDocument(document);
356 		    document.addValue(42, lo);
357 		    wdb.addDocument(document);
358 		    db = wdb;
359 		}
360 
361 		if (!java.util.Arrays.equals(db.getValueLowerBound(42), lo)) {
362 		    System.err.println("Database.getValueLowerBound() doesn't work as expected");
363 		    System.exit(1);
364 		}
365 		if (!java.util.Arrays.equals(db.getValueUpperBound(42), hi)) {
366 		    System.err.println("Database.getValueUpperBound() doesn't work as expected");
367 		    System.exit(1);
368 		}
369 
370 		ValueIterator it = db.valuestreamBegin(42);
371 		if (!java.util.Arrays.equals(it.getValue(), hi)) {
372 		    System.err.println("ValueIterator.getValue() doesn't work as expected");
373 		    System.exit(1);
374 		}
375 	    }
376 	} catch (Exception e) {
377 	    System.err.println("Caught unexpected exception " + e.toString());
378 	    System.exit(1);
379 	}
380     }
381 }
382