001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.gsfret.source.usages;
043:
044: import java.io.File;
045: import java.io.IOException;
046: import java.text.ParseException;
047: import java.util.ArrayList;
048: import java.util.Comparator;
049: import java.util.Enumeration;
050: import java.util.HashMap;
051: import java.util.Iterator;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.Set;
055: import java.util.TreeSet;
056: import java.util.concurrent.atomic.AtomicBoolean;
057: import java.util.logging.Level;
058: import java.util.logging.Logger;
059: import java.util.regex.Matcher;
060: import java.util.regex.Pattern;
061: import org.apache.lucene.document.FieldSelectorResult;
062: import org.apache.lucene.store.NoLockFactory;
063: import org.apache.lucene.analysis.KeywordAnalyzer;
064: import org.apache.lucene.document.DateTools;
065: import org.apache.lucene.document.Document;
066: import org.apache.lucene.document.Field;
067: import org.apache.lucene.document.FieldSelector;
068: import org.apache.lucene.index.IndexReader;
069: import org.apache.lucene.index.IndexWriter;
070: import org.apache.lucene.index.Term;
071: import org.apache.lucene.index.TermDocs;
072: import org.apache.lucene.index.TermEnum;
073: import org.apache.lucene.search.BooleanClause;
074: import org.apache.lucene.search.BooleanQuery;
075: import org.apache.lucene.search.Hit;
076: import org.apache.lucene.search.Hits;
077: import org.apache.lucene.search.IndexSearcher;
078: import org.apache.lucene.search.Searcher;
079: import org.apache.lucene.search.TermQuery;
080: import org.apache.lucene.store.Directory;
081: import org.apache.lucene.store.FSDirectory;
082: import org.apache.lucene.store.RAMDirectory;
083: import org.netbeans.modules.gsf.api.IndexDocument;
084: import org.netbeans.modules.gsf.api.Index.SearchResult;
085: import org.netbeans.modules.gsf.api.NameKind;
086: import org.netbeans.modules.gsf.Language;
087: import org.netbeans.napi.gsfret.source.ClassIndex;
088: import org.netbeans.modules.gsfret.source.util.LowMemoryEvent;
089: import org.netbeans.modules.gsfret.source.util.LowMemoryListener;
090: import org.netbeans.modules.gsfret.source.util.LowMemoryNotifier;
091: import org.openide.util.Exceptions;
092:
093: /**
094: * Lucene interface - Responsible for storing and and querying at the lowest level.
095: * This file is originally from Retouche, the Java Support
096: * infrastructure in NetBeans. I have modified the file as little
097: * as possible to make merging Retouche fixes back as simple as
098: * possible.
099: *
100: *
101: * Rip out the old query stuff.
102: *
103: * @todo Find a faster or more efficient "batch" operation for storing tons of documents
104: * at startup (When scanning the boot path).
105: * @todo Can deletion be better?
106: *
107: * @author Tomas Zezula
108: * @author Tor Norbye
109: */
110: class LuceneIndex extends Index {
111:
112: private static final boolean debugIndexMerging = Boolean
113: .getBoolean("LuceneIndex.debugIndexMerge"); // NOI18N
114: static final String REFERENCES = "gsf"; // NOI18N
115:
116: private static final Logger LOGGER = Logger
117: .getLogger(LuceneIndex.class.getName());
118:
119: private final Directory directory;
120: private Long rootTimeStamp;
121:
122: private IndexReader reader; //Cache, do not use this dirrectly, use getReader
123: private Set<String> rootPkgCache; //Cache, do not use this dirrectly
124:
125: // For debugging purposes only
126: private ClassIndexImpl classIndex;
127: private File cacheRoot;
128:
129: public static Index create(final Language language,
130: final File cacheRoot, ClassIndexImpl classIndex)
131: throws IOException {
132:
133: assert cacheRoot != null && cacheRoot.exists()
134: && cacheRoot.canRead() && cacheRoot.canWrite();
135: LuceneIndex index = new LuceneIndex(language,
136: getReferencesCacheFolder(cacheRoot));
137:
138: // For debugging (lucene browser) only
139: index.classIndex = classIndex;
140: index.cacheRoot = cacheRoot;
141:
142: return index;
143: }
144:
145: /** Creates a new instance of LuceneIndex */
146: private LuceneIndex(final Language language, final File refCacheRoot)
147: throws IOException {
148: super (language);
149: assert refCacheRoot != null;
150: this .directory = FSDirectory.getDirectory(refCacheRoot,
151: NoLockFactory.getNoLockFactory()); //Locking controlled by rwlock
152: }
153:
154: private void regExpSearch(final Pattern pattern, Term startTerm,
155: final IndexReader in,
156: final Set<Term> toSearch/*, final AtomicBoolean cancel*/,
157: boolean caseSensitive) throws IOException/*, InterruptedException*/{
158: final String startText = startTerm.text();
159: String startPrefix;
160: if (startText.length() > 0) {
161: final StringBuilder startBuilder = new StringBuilder();
162: startBuilder.append(startText.charAt(0));
163: for (int i = 1; i < startText.length(); i++) {
164: char c = startText.charAt(i);
165: if (!Character.isJavaIdentifierPart(c)) {
166: break;
167: }
168: startBuilder.append(c);
169: }
170: // TODO - if startText==startPrefix keep startTerm alone
171: startPrefix = startBuilder.toString();
172: // TODO: The java version of lucene index reassigned the start term here
173: // startTerm = caseSensitive ? DocumentUtil.simpleNameTerm(startPrefix) : DocumentUtil.caseInsensitiveNameTerm(startPrefix);
174: } else {
175: startPrefix = startText;
176: }
177: final String camelField = startTerm.field();
178: final TermEnum en = in.terms(startTerm);
179: try {
180: do {
181: Term term = en.term();
182: if (term != null && camelField == term.field()
183: && term.text().startsWith(startPrefix)) {
184: final Matcher m = pattern.matcher(term.text());
185: if (m.matches()) {
186: toSearch.add(term);
187: }
188: } else {
189: break;
190: }
191: } while (en.next());
192: } finally {
193: en.close();
194: }
195: }
196:
197: private void prefixSearch(Term nameTerm, final IndexReader in,
198: final Set<Term> toSearch/*, final AtomicBoolean cancel*/)
199: throws IOException/*, InterruptedException*/{
200: final String prefixField = nameTerm.field();
201: final String name = nameTerm.text();
202: final TermEnum en = in.terms(nameTerm);
203: try {
204: do {
205: Term term = en.term();
206: if (term != null && prefixField == term.field()
207: && term.text().startsWith(name)) {
208: toSearch.add(term);
209: } else {
210: break;
211: }
212: } while (en.next());
213: } finally {
214: en.close();
215: }
216: }
217:
218: public boolean isUpToDate(String resourceName, long timeStamp)
219: throws IOException {
220: // Don't do anything for preindexed filesystems
221: if (Index.isPreindexed(cacheRoot)) {
222: return true;
223: }
224:
225: if (!isValid(false)) {
226: return false;
227: }
228: try {
229: Searcher searcher = new IndexSearcher(this .getReader());
230: try {
231: Hits hits;
232: if (resourceName == null) {
233: synchronized (this ) {
234: if (this .rootTimeStamp != null) {
235: return rootTimeStamp.longValue() >= timeStamp;
236: }
237: }
238: hits = searcher.search(new TermQuery(DocumentUtil
239: .rootDocumentTerm()));
240: } else {
241: hits = searcher.search(DocumentUtil
242: .binaryNameQuery(resourceName));
243: }
244:
245: assert hits.length() <= 1;
246: if (hits.length() == 0) {
247: return false;
248: } else {
249: try {
250: Hit hit = (Hit) hits.iterator().next();
251: long cacheTime = DocumentUtil.getTimeStamp(hit
252: .getDocument());
253: if (resourceName == null) {
254: synchronized (this ) {
255: this .rootTimeStamp = new Long(cacheTime);
256: }
257: }
258: return cacheTime >= timeStamp;
259: } catch (ParseException pe) {
260: throw new IOException();
261: }
262: }
263: } finally {
264: searcher.close();
265: }
266: } catch (java.io.FileNotFoundException fnf) {
267: this .clear();
268: return false;
269: }
270: }
271:
272: private FieldSelector FILE_AND_TIMESTAMP = new FieldSelector() {
273: public FieldSelectorResult accept(String key) {
274: return DocumentUtil.FIELD_FILENAME.equals(key)
275: || DocumentUtil.FIELD_TIME_STAMP.equals(key) ? FieldSelectorResult.LOAD
276: : FieldSelectorResult.NO_LOAD;
277: }
278: };
279:
280: private class CustomFieldSelector implements FieldSelector {
281: private Set<String> includeFields;
282:
283: CustomFieldSelector(Set<String> includeFields) {
284: this .includeFields = includeFields;
285: }
286:
287: public FieldSelectorResult accept(String key) {
288: // Always load the filename since clients don't know about it but it's needed
289: // to call getPersistentUrl()
290: boolean include = includeFields.contains(key)
291: || key.equals(DocumentUtil.FIELD_FILENAME);
292: return include ? FieldSelectorResult.LOAD
293: : FieldSelectorResult.NO_LOAD;
294: }
295: }
296:
297: public Map<String, String> getTimeStamps() throws IOException {
298: if (!isValid(false)) {
299: return null;
300: }
301: final IndexReader in = getReader();
302: Map<String, String> result = new HashMap(2 * in.numDocs());
303: for (int i = 0, n = in.numDocs(); i < n; i++) {
304: if (in.isDeleted(i)) {
305: continue;
306: }
307: Document document = in.document(i, FILE_AND_TIMESTAMP);
308: // TODO - use a query instead! Faster iteration!
309: String timestamp = document
310: .get(DocumentUtil.FIELD_TIME_STAMP);
311: String filename = document.get(DocumentUtil.FIELD_FILENAME);
312: if (timestamp != null && filename != null) {
313: // Ugh - what if I already have another document for the same file
314: // in here - shouldn't I pick the oldest timestamp? Or is it
315: // the responsibility of the clients to always update all documents
316: // at the same time for a given file?
317: result.put(filename, timestamp);
318: }
319: }
320:
321: return result;
322: }
323:
324: public boolean isValid(boolean tryOpen) throws IOException {
325: boolean res = IndexReader.indexExists(this .directory);
326: if (res && tryOpen) {
327: try {
328: getReader();
329: } catch (java.io.IOException e) {
330: res = false;
331: clear();
332: }
333: }
334: return res;
335: }
336:
337: public synchronized void clear() throws IOException {
338: this .close();
339: final String[] content = this .directory.list();
340: for (String file : content) {
341: directory.deleteFile(file);
342: }
343: }
344:
345: public synchronized void close() throws IOException {
346: try {
347: if (this .reader != null) {
348: this .reader.close();
349: this .reader = null;
350: }
351: } finally {
352: this .directory.close();
353: }
354: }
355:
356: public @Override
357: String toString() {
358: return this .directory.toString();
359: }
360:
361: private synchronized IndexReader getReader() throws IOException {
362: if (this .reader == null) {
363: this .reader = IndexReader.open(this .directory);
364: }
365: return this .reader;
366: }
367:
368: private synchronized IndexWriter getWriter(final boolean create)
369: throws IOException {
370: if (this .reader != null) {
371: this .reader.close();
372: this .reader = null;
373: }
374: IndexWriter writer = new IndexWriter(this .directory,
375: new KeywordAnalyzer(), create);
376: return writer;
377: }
378:
379: private static File getReferencesCacheFolder(final File cacheRoot)
380: throws IOException {
381: File refRoot = new File(cacheRoot, REFERENCES);
382: if (!refRoot.exists()) {
383: refRoot.mkdir();
384: }
385: return refRoot;
386: }
387:
388: private static class LMListener implements LowMemoryListener {
389:
390: private AtomicBoolean lowMemory = new AtomicBoolean(false);
391:
392: public void lowMemory(LowMemoryEvent event) {
393: lowMemory.set(true);
394: }
395: }
396:
397: // BEGIN TOR MODIFICATIONS
398: public void store(String fileUrl, List<IndexDocument> documents)
399: throws IOException {
400: assert ClassIndexManager.holdsWriteLock();
401: this .rootPkgCache = null;
402: boolean create = !isValid(false);
403: if (!create) {
404: IndexReader in = getReader();
405:
406: final Searcher searcher = new IndexSearcher(in);
407: try {
408: if (fileUrl != null) {
409: BooleanQuery query = new BooleanQuery();
410: query.add(new TermQuery(new Term(
411: DocumentUtil.FIELD_FILENAME, fileUrl)),
412: BooleanClause.Occur.MUST);
413:
414: Hits hits = searcher.search(query);
415: //if (hits.length()>1) {
416: // // Uhm -- don't we put MULTIPLE documents into the same item now?
417: // // This isn't abnormal, is it?
418: // LOGGER.getLogger("global").warning("Multiple(" + hits.length() + ") index entries for key: " + key + " where value: " + value + " where cacheRoot=" + cacheRoot); //NOI18N
419: //}
420: for (int i = 0; i < hits.length(); i++) {
421: in.deleteDocument(hits.id(i));
422: }
423: }
424: in.deleteDocuments(DocumentUtil.rootDocumentTerm());
425: } finally {
426: searcher.close();
427: }
428: }
429: long timeStamp = System.currentTimeMillis();
430: store(documents, create, timeStamp, fileUrl);
431: }
432:
433: private void store(List<IndexDocument> d, final boolean create,
434: final long timeStamp, final String filename)
435: throws IOException {
436: List<IndexDocumentImpl> documents = (List<IndexDocumentImpl>) (List) d;
437: final IndexWriter out = getWriter(create);
438: try {
439: if (debugIndexMerging) {
440: out.setInfoStream(System.err);
441: }
442: final LuceneIndexMBean indexSettings = LuceneIndexMBeanImpl
443: .getDefault();
444: if (indexSettings != null) {
445: out.setMergeFactor(indexSettings.getMergeFactor());
446: out.setMaxMergeDocs(indexSettings.getMaxMergeDocs());
447: out.setMaxBufferedDocs(indexSettings
448: .getMaxBufferedDocs());
449: }
450: LowMemoryNotifier lm = LowMemoryNotifier.getDefault();
451: LMListener lmListener = new LMListener();
452: lm.addLowMemoryListener(lmListener);
453: Directory memDir = null;
454: IndexWriter activeOut = null;
455: if (lmListener.lowMemory.getAndSet(false)) {
456: activeOut = out;
457: } else {
458: memDir = new RAMDirectory();
459: activeOut = new IndexWriter(memDir,
460: new KeywordAnalyzer(), true);
461: }
462: try {
463: activeOut.addDocument(DocumentUtil
464: .createRootTimeStampDocument(timeStamp));
465: if (documents != null && documents.size() > 0) {
466: for (IndexDocumentImpl document : documents) {
467: Document newDoc = new Document();
468: newDoc
469: .add(new Field(
470: DocumentUtil.FIELD_TIME_STAMP,
471: DateTools
472: .timeToString(
473: timeStamp,
474: DateTools.Resolution.MILLISECOND),
475: Field.Store.YES, Field.Index.NO));
476: if (filename != null) {
477: newDoc.add(new Field(
478: DocumentUtil.FIELD_FILENAME,
479: filename, Field.Store.YES,
480: Field.Index.UN_TOKENIZED));
481: }
482:
483: for (int i = 0, n = document.indexedKeys.size(); i < n; i++) {
484: String key = document.indexedKeys.get(i);
485: String value = document.indexedValues
486: .get(i);
487: assert key != null && value != null : "key="
488: + key + ", value=" + value;
489: Field field = new Field(key, value,
490: Field.Store.YES,
491: Field.Index.UN_TOKENIZED);
492: newDoc.add(field);
493: }
494:
495: for (int i = 0, n = document.unindexedKeys
496: .size(); i < n; i++) {
497: String key = document.unindexedKeys.get(i);
498: String value = document.unindexedValues
499: .get(i);
500: assert key != null && value != null : "key="
501: + key + ", value=" + value;
502: Field field = new Field(key, value,
503: Field.Store.YES, Field.Index.NO);
504: newDoc.add(field);
505: }
506:
507: activeOut.addDocument(newDoc);
508: }
509: } else if (filename != null) {
510: Document newDoc = new Document();
511: newDoc.add(new Field(DocumentUtil.FIELD_TIME_STAMP,
512: DateTools.timeToString(timeStamp,
513: DateTools.Resolution.MILLISECOND),
514: Field.Store.YES, Field.Index.NO));
515: newDoc.add(new Field(DocumentUtil.FIELD_FILENAME,
516: filename, Field.Store.YES,
517: Field.Index.UN_TOKENIZED));
518: activeOut.addDocument(newDoc);
519: }
520:
521: if (memDir != null
522: && lmListener.lowMemory.getAndSet(false)) {
523: activeOut.close();
524: out.addIndexes(new Directory[] { memDir });
525: memDir = new RAMDirectory();
526: activeOut = new IndexWriter(memDir,
527: new KeywordAnalyzer(), true);
528: }
529: if (memDir != null) {
530: activeOut.close();
531: out.addIndexes(new Directory[] { memDir });
532: activeOut = null;
533: memDir = null;
534: }
535: synchronized (this ) {
536: this .rootTimeStamp = new Long(timeStamp);
537: }
538: } finally {
539: lm.removeLowMemoryListener(lmListener);
540: }
541: } finally {
542: out.close();
543: }
544: }
545:
546: @SuppressWarnings("unchecked")
547: // NOI18N, unchecked - lucene has source 1.4
548: public void search(final String primaryField, final String name,
549: final NameKind kind,
550: final Set<ClassIndex.SearchScope> scope,
551: final Set<SearchResult> result, final Set<String> terms)
552: throws IOException {
553: if (!isValid(false)) {
554: LOGGER.fine(String.format("LuceneIndex[%s] is invalid!\n",
555: this .toString()));
556: return;
557: }
558:
559: assert name != null;
560: final Set<Term> toSearch = new TreeSet<Term>(
561: new Comparator<Term>() {
562: public int compare(Term t1, Term t2) {
563: int ret = t1.field().compareTo(t2.field());
564: if (ret == 0) {
565: ret = t1.text().compareTo(t2.text());
566: }
567: return ret;
568: }
569: });
570:
571: final IndexReader in = getReader();
572: switch (kind) {
573: case EXACT_NAME: {
574: toSearch.add(new Term(primaryField, name));
575: break;
576: }
577: case PREFIX:
578: if (name.length() == 0) {
579: //Special case (all) handle in different way
580: gsfEmptyPrefixSearch(in, result, primaryField);
581: return;
582: } else {
583: final Term nameTerm = new Term(primaryField, name);
584: prefixSearch(nameTerm, in, toSearch);
585: break;
586: }
587: case CASE_INSENSITIVE_PREFIX:
588: if (name.length() == 0) {
589: //Special case (all) handle in different way
590: gsfEmptyPrefixSearch(in, result, primaryField);
591: return;
592: } else {
593: final Term nameTerm = new Term(primaryField, name
594: .toLowerCase());
595: prefixSearch(nameTerm, in, toSearch);
596: break;
597: }
598: case CAMEL_CASE:
599: if (name.length() == 0) {
600: search(primaryField, name,
601: NameKind.CASE_INSENSITIVE_PREFIX, scope,
602: result, terms);
603: return;
604: }
605: {
606: StringBuilder sb = new StringBuilder();
607: String prefix = null;
608: int lastIndex = 0;
609: int index;
610: do {
611: index = findNextUpper(name, lastIndex + 1);
612: String token = name.substring(lastIndex,
613: index == -1 ? name.length() : index);
614: if (lastIndex == 0) {
615: prefix = token;
616: }
617: sb.append(token);
618: // TODO - add in Ruby chars here?
619: sb
620: .append(index != -1 ? "[\\p{javaLowerCase}\\p{Digit}_\\$]*"
621: : ".*"); // NOI18N
622: lastIndex = index;
623: } while (index != -1);
624:
625: final Pattern pattern = Pattern.compile(sb.toString());
626: final Term nameTerm = new Term(primaryField, prefix);
627: regExpSearch(pattern, nameTerm, in,
628: toSearch/*,cancel*/, true);
629: }
630: break;
631: case CASE_INSENSITIVE_REGEXP:
632: if (name.length() == 0) {
633: search(primaryField, name,
634: NameKind.CASE_INSENSITIVE_PREFIX, scope,
635: result, terms);
636: return;
637: } else {
638: final Pattern pattern = Pattern.compile(name,
639: Pattern.CASE_INSENSITIVE);
640: if (Character.isJavaIdentifierStart(name.charAt(0))) {
641: regExpSearch(pattern, new Term(primaryField, name
642: .toLowerCase()), in, toSearch/*,cancel*/,
643: false); //XXX: Locale
644: } else {
645: regExpSearch(pattern, new Term(primaryField, ""),
646: in, toSearch/*, cancel*/, false); //NOI18N
647: }
648: break;
649: }
650: case REGEXP:
651: if (name.length() == 0) {
652: search(primaryField, name, NameKind.PREFIX, scope,
653: result, terms);
654: return;
655: } else {
656: final Pattern pattern = Pattern.compile(name);
657: if (Character.isJavaIdentifierStart(name.charAt(0))) {
658: regExpSearch(pattern, new Term(primaryField, name),
659: in, toSearch/*, cancel*/, true);
660: } else {
661: regExpSearch(pattern, new Term(primaryField, ""),
662: in, toSearch/*, cancel*/, true); //NOI18N
663: }
664: break;
665: }
666: default:
667: throw new UnsupportedOperationException(kind.toString());
668: }
669: TermDocs tds = in.termDocs();
670: if (LOGGER.isLoggable(Level.FINE)) {
671: LOGGER
672: .fine(String
673: .format(
674: "LuceneIndex.getDeclaredTypes[%s] returned %d elements\n",
675: this .toString(), toSearch.size()));
676: }
677: final Iterator<Term> it = toSearch.iterator();
678: Set<Integer> docNums = new TreeSet<Integer>();
679: Map<Integer, List<String>> matches = new HashMap<Integer, List<String>>();
680: while (it.hasNext()) {
681: Term next = it.next();
682: tds.seek(next);
683: while (tds.next()) {
684: Integer docNum = Integer.valueOf(tds.doc());
685: List<String> matchTerms = matches.get(docNum);
686: if (matchTerms == null) {
687: matchTerms = new ArrayList<String>();
688: matches.put(docNum, matchTerms);
689: }
690: matchTerms.add(next.text());
691: docNums.add(docNum);
692: }
693: }
694: for (Integer docNum : docNums) {
695: final Document doc = in.document(docNum);
696:
697: List<String> matchList = matches.get(docNum);
698: FilteredDocumentSearchResult map = new FilteredDocumentSearchResult(
699: doc, primaryField, matchList, docNum);
700: result.add(map);
701: }
702: }
703:
704: private static int findNextUpper(String text, int offset) {
705:
706: for (int i = offset; i < text.length(); i++) {
707: if (Character.isUpperCase(text.charAt(i))) {
708: return i;
709: }
710: }
711: return -1;
712: }
713:
714: // TODO: Create a filtered DocumentSearchResult here which
715: // contains matches for a given document.
716:
717: private class DocumentSearchResult implements SearchResult {
718: private Document doc;
719: private int docId;
720:
721: private DocumentSearchResult(Document doc, int docId) {
722: this .doc = doc;
723: this .docId = docId;
724: }
725:
726: public String getValue(String key) {
727: return doc.get(key);
728: }
729:
730: public String[] getValues(String key) {
731: return doc.getValues(key);
732: }
733:
734: public String toString() {
735: StringBuilder sb = new StringBuilder();
736: Enumeration en = doc.fields();
737: while (en.hasMoreElements()) {
738: Field f = (Field) en.nextElement();
739: sb.append(f.name());
740: sb.append(":");
741: sb.append(f.stringValue());
742: sb.append("\n");
743: }
744:
745: return sb.toString();
746: }
747:
748: public int getDocumentNumber() {
749: return docId;
750: }
751:
752: public Object getDocument() {
753: return doc;
754: }
755:
756: public Object getIndexReader() {
757: try {
758: return getReader();
759: } catch (IOException ioe) {
760: Exceptions.printStackTrace(ioe);
761: return null;
762: }
763: }
764:
765: public Object getIndex() {
766: return LuceneIndex.this .classIndex;
767: }
768:
769: public File getSegment() {
770: return LuceneIndex.this .cacheRoot;
771: }
772:
773: public String getPersistentUrl() {
774: return getValue(DocumentUtil.FIELD_FILENAME);
775: }
776: }
777:
778: private class FilteredDocumentSearchResult implements SearchResult {
779: private Document doc;
780: private int docId;
781: private String primaryKey;
782: private List<String> primaryValues;
783:
784: private FilteredDocumentSearchResult(Document doc,
785: String primaryKey, List<String> primaryValues, int docId) {
786: this .doc = doc;
787: this .primaryKey = primaryKey;
788: this .primaryValues = primaryValues;
789: this .docId = docId;
790: }
791:
792: public String getValue(String key) {
793: if (key.equals(primaryKey)) {
794: if (primaryValues.size() > 0) {
795: return primaryValues.get(0);
796: } else {
797: return null;
798: }
799: }
800: return doc.get(key);
801: }
802:
803: public String[] getValues(String key) {
804: if (key.equals(primaryKey)) {
805: return primaryValues.toArray(new String[primaryValues
806: .size()]);
807: }
808: return doc.getValues(key);
809: }
810:
811: public String getPersistentUrl() {
812: return getValue(DocumentUtil.FIELD_FILENAME);
813: }
814:
815: @Override
816: public String toString() {
817: StringBuilder sb = new StringBuilder();
818: Enumeration en = doc.fields();
819: while (en.hasMoreElements()) {
820: Field f = (Field) en.nextElement();
821: if (f.name().equals(primaryKey)) {
822: sb.append(primaryKey);
823: sb.append(":");
824: sb.append(primaryValues.toString());
825: } else {
826: sb.append(f.name());
827: sb.append(":");
828: sb.append(f.stringValue());
829: }
830: sb.append("\n");
831: }
832:
833: return sb.toString();
834: }
835:
836: public int getDocumentNumber() {
837: return docId;
838: }
839:
840: public Object getDocument() {
841: return doc;
842: }
843:
844: public Object getIndexReader() {
845: try {
846: return getReader();
847: } catch (IOException ioe) {
848: Exceptions.printStackTrace(ioe);
849: return null;
850: }
851: }
852:
853: public Object getIndex() {
854: return LuceneIndex.this .classIndex;
855: }
856:
857: public File getSegment() {
858: return LuceneIndex.this .cacheRoot;
859: }
860: }
861:
862: private <T> void gsfEmptyPrefixSearch(final IndexReader in,
863: final Set<SearchResult> result, final String primaryField)
864: throws IOException {
865: final int bound = in.maxDoc();
866: for (int i = 0; i < bound; i++) {
867: if (!in.isDeleted(i)) {
868: final Document doc = in.document(i);
869: if (doc != null) {
870: SearchResult map = new DocumentSearchResult(doc, i);
871: result.add(map);
872: }
873: }
874: }
875: }
876:
877: // For symbol dumper only
878: public IndexReader getDumpIndexReader() throws IOException {
879: return getReader();
880: }
881: }
|