0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.editor.lib2.search;
0043:
0044: import java.util.Map;
0045: import java.util.regex.Matcher;
0046: import java.util.regex.Pattern;
0047: import java.util.regex.PatternSyntaxException;
0048: import javax.swing.text.BadLocationException;
0049: import javax.swing.text.Document;
0050: import org.netbeans.lib.editor.util.swing.DocumentUtilities;
0051: import org.netbeans.modules.editor.lib2.DocUtils;
0052: import org.openide.DialogDisplayer;
0053: import org.openide.NotifyDescriptor;
0054: import org.openide.util.NbBundle;
0055:
0056: /**
0057: *
0058: * @author Martin Roskanin
0059: */
0060: public class DocumentFinder {
0061:
0062: private static FalseBlocksFinder falseBlocksFinder;
0063: private static FalseFinder falseFinder;
0064: private static WholeWordsBlocksFinder wholeWordsBlocksFinder;
0065: private static RegExpBlocksFinder regExpBlocksFinder;
0066: private static StringBlocksFinder stringBlocksFinder;
0067: private static WholeWordsBwdFinder wholeWordsBwdFinder;
0068: private static WholeWordsFwdFinder wholeWordsFwdFinder;
0069: private static RegExpBwdFinder regExpBwdFinder;
0070: private static RegExpFwdFinder regExpFwdFinder;
0071: private static StringBwdFinder stringBwdFinder;
0072: private static StringFwdFinder stringFwdFinder;
0073:
0074: /** Creates a new instance of DocumentFinder */
0075: private DocumentFinder() {
0076: }
0077:
0078: private static DocFinder getFinder(Document doc, Map searchProps,
0079: boolean oppositeDir, boolean blocksFinder) {
0080: String text = (String) searchProps
0081: .get(EditorFindSupport.FIND_WHAT);
0082: if (text == null || text.length() == 0) {
0083: if (blocksFinder) {
0084: if (falseBlocksFinder == null) {
0085: falseBlocksFinder = new FalseBlocksFinder();
0086: }
0087: return falseBlocksFinder;
0088: } else {
0089: if (falseFinder == null) {
0090: falseFinder = new FalseFinder();
0091: }
0092: return falseFinder;
0093: }
0094: }
0095:
0096: Boolean b = (Boolean) searchProps
0097: .get(EditorFindSupport.FIND_BACKWARD_SEARCH);
0098: boolean bwdSearch = (b != null && b.booleanValue());
0099: if (oppositeDir) { // negate for opposite direction search
0100: bwdSearch = !bwdSearch;
0101: }
0102:
0103: b = (Boolean) searchProps
0104: .get(EditorFindSupport.FIND_MATCH_CASE);
0105: boolean matchCase = (b != null && b.booleanValue());
0106: b = (Boolean) searchProps
0107: .get(EditorFindSupport.FIND_SMART_CASE);
0108: boolean smartCase = (b != null && b.booleanValue());
0109: b = (Boolean) searchProps
0110: .get(EditorFindSupport.FIND_WHOLE_WORDS);
0111: boolean wholeWords = (b != null && b.booleanValue());
0112:
0113: if (smartCase && !matchCase) {
0114: int cnt = text.length();
0115: for (int i = 0; i < cnt; i++) {
0116: if (Character.isUpperCase(text.charAt(i))) {
0117: matchCase = true;
0118: }
0119: }
0120: }
0121:
0122: b = (Boolean) searchProps.get(EditorFindSupport.FIND_REG_EXP);
0123: boolean regExpSearch = (b != null && b.booleanValue());
0124:
0125: Pattern pattern = null;
0126: if (regExpSearch) {
0127: try {
0128: pattern = PatternCache.getPattern(text, matchCase);
0129: if (pattern == null) {
0130: pattern = (matchCase) ? Pattern.compile(text,
0131: Pattern.MULTILINE) : Pattern.compile(text,
0132: Pattern.MULTILINE
0133: | Pattern.CASE_INSENSITIVE); // NOI18N
0134: PatternCache.putPattern(text, matchCase, pattern);
0135: }
0136: } catch (PatternSyntaxException pse) {
0137: if (!blocksFinder) {
0138: NotifyDescriptor msg = new NotifyDescriptor.Message(
0139: pse.getDescription(),
0140: NotifyDescriptor.ERROR_MESSAGE);
0141: msg.setTitle(NbBundle.getBundle(
0142: DocumentFinder.class).getString(
0143: "pattern-error-dialog-title")); //NOI18N
0144: DialogDisplayer.getDefault().notify(msg);
0145: }
0146: PatternCache.putPattern(text, matchCase, null);
0147: return null;
0148: }
0149: } else {
0150: PatternCache.clear();
0151: }
0152:
0153: if (blocksFinder) {
0154: if (wholeWords && !regExpSearch) {
0155: if (wholeWordsBlocksFinder == null) {
0156: wholeWordsBlocksFinder = new WholeWordsBlocksFinder();
0157: }
0158: wholeWordsBlocksFinder.setParams(doc, text, matchCase);
0159: return wholeWordsBlocksFinder;
0160: } else {
0161: if (regExpSearch) {
0162: if (regExpBlocksFinder == null) {
0163: regExpBlocksFinder = new RegExpBlocksFinder();
0164: }
0165: regExpBlocksFinder.setParams(pattern, matchCase);
0166: return regExpBlocksFinder;
0167: } else {
0168: if (stringBlocksFinder == null) {
0169: stringBlocksFinder = new StringBlocksFinder();
0170: }
0171: stringBlocksFinder.setParams(text, matchCase);
0172: return stringBlocksFinder;
0173: }
0174: }
0175: } else {
0176: if (wholeWords && !regExpSearch) {
0177: if (bwdSearch) {
0178: if (wholeWordsBwdFinder == null) {
0179: wholeWordsBwdFinder = new WholeWordsBwdFinder();
0180: }
0181: wholeWordsBwdFinder.setParams(doc, text, matchCase);
0182: return wholeWordsBwdFinder;
0183: } else {
0184: if (wholeWordsFwdFinder == null) {
0185: wholeWordsFwdFinder = new WholeWordsFwdFinder();
0186: }
0187: wholeWordsFwdFinder.setParams(doc, text, matchCase);
0188: return wholeWordsFwdFinder;
0189: }
0190: } else {
0191: if (regExpSearch) {
0192: if (bwdSearch) {
0193: if (regExpBwdFinder == null) {
0194: regExpBwdFinder = new RegExpBwdFinder();
0195: }
0196: regExpBwdFinder.setParams(pattern, matchCase);
0197: return regExpBwdFinder;
0198: } else {
0199: if (regExpFwdFinder == null) {
0200: regExpFwdFinder = new RegExpFwdFinder();
0201: }
0202: regExpFwdFinder.setParams(pattern, matchCase);
0203: return regExpFwdFinder;
0204: }
0205: } else {
0206: if (bwdSearch) {
0207: if (stringBwdFinder == null) {
0208: stringBwdFinder = new StringBwdFinder();
0209: }
0210: stringBwdFinder.setParams(text, matchCase);
0211: return stringBwdFinder;
0212: } else {
0213: if (stringFwdFinder == null) {
0214: stringFwdFinder = new StringFwdFinder();
0215: }
0216: stringFwdFinder.setParams(text, matchCase);
0217: return stringFwdFinder;
0218: }
0219: }
0220: }
0221: }
0222: }
0223:
0224: private static FindReplaceResult findReplaceImpl(
0225: String replaceText, Document doc, int startOffset,
0226: int endOffset, Map props, boolean oppositeDir)
0227: throws BadLocationException {
0228: int ret[] = new int[2];
0229: if (endOffset == -1) {
0230: endOffset = doc.getLength();
0231: }
0232: if (startOffset > endOffset) {
0233: int temp = startOffset;
0234: startOffset = endOffset;
0235: endOffset = temp;
0236: }
0237: DocFinder finder = getFinder(doc, props, oppositeDir, false);
0238: if (finder == null) {
0239: return null;
0240: }
0241: finder.reset();
0242: CharSequence cs = DocumentUtilities.getText(doc, startOffset,
0243: endOffset - startOffset);
0244: if (cs == null)
0245: return null;
0246: int findRet = finder.find(startOffset, cs);
0247: if (!finder.isFound()) {
0248: ret[0] = -1;
0249: return new FindReplaceResult(ret, replaceText);
0250: }
0251: ret[0] = startOffset + findRet;
0252:
0253: if (finder instanceof StringFinder) {
0254: int length = ((StringFinder) finder).getFoundLength();
0255: ret[1] = ret[0] + length;
0256: }
0257:
0258: if (finder instanceof RegExpFinder) {
0259: Matcher matcher = ((RegExpFinder) finder).getMatcher();
0260: if (matcher != null && replaceText != null) {
0261: CharSequence foundString = cs.subSequence(ret[0]
0262: - startOffset, ret[1] - startOffset);
0263: matcher.reset(foundString);
0264: if (matcher.find()) {
0265: try {
0266: replaceText = matcher.replaceFirst(replaceText);
0267: } catch (IndexOutOfBoundsException ioobe) {
0268: NotifyDescriptor msg = new NotifyDescriptor.Message(
0269: ioobe.getLocalizedMessage(),
0270: NotifyDescriptor.ERROR_MESSAGE);
0271: msg.setTitle(NbBundle.getBundle(
0272: DocumentFinder.class).getString(
0273: "pattern-error-dialog-title")); //NOI18N
0274: DialogDisplayer.getDefault().notify(msg);
0275: return null;
0276: }
0277: }
0278: }
0279: }
0280: return new FindReplaceResult(ret, replaceText);
0281: }
0282:
0283: /**
0284: * Finds in document
0285: *
0286: * @param doc document where to find
0287: * @param startOffset offset in the document where the search will start
0288: * @param endOffset offset where the search will end with reporting
0289: * that nothing was found.
0290: * @param props find properties
0291: */
0292: public static int[] find(Document doc, int startOffset,
0293: int endOffset, Map props, boolean oppositeDir)
0294: throws BadLocationException {
0295: FindReplaceResult result = findReplaceImpl(null, doc,
0296: startOffset, endOffset, props, oppositeDir);
0297: if (result == null) {
0298: return null;
0299: }
0300:
0301: return result.getFoundPositions();
0302: }
0303:
0304: public static int[] findBlocks(Document doc, int startOffset,
0305: int endOffset, Map props, int blocks[])
0306: throws BadLocationException {
0307: BlocksFinder finder = (BlocksFinder) getFinder(doc, props,
0308: false, true);
0309: if (finder == null) {
0310: return blocks;
0311: }
0312: CharSequence cs = DocumentUtilities.getText(doc, startOffset,
0313: endOffset - startOffset);
0314: if (cs == null) {
0315: return null;
0316: }
0317: synchronized (finder) {
0318: finder.reset();
0319: finder.setBlocks(blocks);
0320: finder.find(startOffset, cs);
0321: int ret[] = finder.getBlocks();
0322: return ret;
0323: }
0324: }
0325:
0326: /**
0327: * Finds the searching string and substitute replace expression in case of
0328: * regexp backreferences.
0329: * @return FindReplaceResult, that contains positions of found string and substituted replace expression
0330: */
0331: public static FindReplaceResult findReplaceResult(
0332: String replaceString, Document doc, int startOffset,
0333: int endOffset, Map props, boolean oppositeDir)
0334: throws BadLocationException {
0335: return findReplaceImpl(replaceString, doc, startOffset,
0336: endOffset, props, oppositeDir);
0337: }
0338:
0339: private interface DocFinder {
0340:
0341: public int find(int initOffset, CharSequence data);
0342:
0343: public boolean isFound();
0344:
0345: public void reset();
0346: }
0347:
0348: private static final class FalseBlocksFinder extends
0349: AbstractBlocksFinder {
0350:
0351: public int find(int initOffset, CharSequence data) {
0352: return -1;
0353: }
0354:
0355: }
0356:
0357: /** Request non-existent position immediately */
0358: private static class FalseFinder extends AbstractFinder implements
0359: StringFinder {
0360:
0361: public int find(int initOffset, CharSequence data) {
0362: return -1;
0363: }
0364:
0365: public int getFoundLength() {
0366: return 0;
0367: }
0368:
0369: }
0370:
0371: private static abstract class AbstractBlocksFinder extends
0372: AbstractFinder implements BlocksFinder {
0373:
0374: private static int[] EMPTY_INT_ARRAY = new int[0];
0375:
0376: private int[] blocks = EMPTY_INT_ARRAY;
0377:
0378: private int blocksInd;
0379:
0380: private boolean closed;
0381:
0382: public void reset() {
0383: super .reset();
0384: blocksInd = 0;
0385: closed = false;
0386: }
0387:
0388: public final int[] getBlocks() {
0389: if (!closed) { // not closed yet
0390: closeBlocks();
0391: closed = true;
0392: }
0393: return blocks;
0394: }
0395:
0396: public final void setBlocks(int[] blocks) {
0397: this .blocks = blocks;
0398: blocksInd = 0;
0399: closed = false;
0400: }
0401:
0402: protected final void addBlock(int blkStartPos, int blkEndPos) {
0403: if (blocksInd + 2 > blocks.length) {
0404: int[] dbl = new int[Math.max(10, (blocksInd + 1) * 2)];
0405: System.arraycopy(blocks, 0, dbl, 0, blocks.length);
0406: blocks = dbl;
0407: }
0408:
0409: blocks[blocksInd] = blkStartPos;
0410: blocks[blocksInd + 1] = blkEndPos;
0411:
0412: blocksInd += 2;
0413: }
0414:
0415: /** Insert closing sequence [-1, -1] */
0416: protected final void closeBlocks() {
0417: addBlock(-1, -1);
0418: }
0419:
0420: public final String debugBlocks() {
0421: StringBuffer buf = new StringBuffer();
0422: int ind = 0;
0423: while (blocks[ind] != -1) {
0424: buf.append((ind / 2 + 1) + ": [" + blocks[ind] + ", "
0425: + blocks[ind + 1] + "]\n"); // NOI18N
0426: ind += 2;
0427: }
0428: return buf.toString();
0429: }
0430:
0431: }
0432:
0433: /** Finder that constructs [begin-pos, end-pos] blocks.
0434: * This is useful for highlight-search draw layer.
0435: * The block-finders are always forward-search finders.
0436: */
0437: private interface BlocksFinder extends DocFinder {
0438:
0439: /** Set the array into which the finder puts
0440: * the position blocks. If the length of array is not sufficient
0441: * the finder extends the array. The last block is set to [-1, -1].
0442: */
0443: public void setBlocks(int[] blocks);
0444:
0445: /** Get the array filled with position blocks. It is either
0446: * original array passed to setBlocks() or the new array
0447: * if the finder extended the array.
0448: */
0449: public int[] getBlocks();
0450:
0451: }
0452:
0453: /** Abstract finder implementation. The only <CODE>find()</CODE>
0454: * method must be redefined.
0455: */
0456: private static abstract class AbstractFinder implements DocFinder {
0457:
0458: /** Was the string found? */
0459: protected boolean found;
0460:
0461: /** Was the string found? */
0462: public final boolean isFound() {
0463: return found;
0464: }
0465:
0466: /** Reset the finder */
0467: public void reset() {
0468: found = false;
0469: }
0470:
0471: }
0472:
0473: /** Finder that looks for some search expression expressed by string.
0474: * It can be either simple string
0475: * or some form of regular expression expressed by string.
0476: */
0477: private interface StringFinder extends DocFinder {
0478:
0479: /** Get the length of the found string. This is useful
0480: * for regular expressions, because the length of the regular
0481: * expression can be different than the length of the string
0482: * that matched the expression.
0483: */
0484: public int getFoundLength();
0485:
0486: }
0487:
0488: /** String forward finder that finds whole words only
0489: * and that creates position blocks.
0490: * There are some speed optimizations attempted.
0491: */
0492: private static final class WholeWordsBlocksFinder extends
0493: AbstractBlocksFinder {
0494:
0495: char chars[];
0496:
0497: int stringInd;
0498:
0499: boolean matchCase;
0500:
0501: boolean insideWord;
0502:
0503: boolean firstCharWordPart;
0504:
0505: boolean wordFound;
0506:
0507: Document doc;
0508:
0509: public WholeWordsBlocksFinder() {
0510: }
0511:
0512: public void setParams(Document doc, String s, boolean matchCase) {
0513: this .matchCase = matchCase;
0514: this .doc = doc;
0515: chars = (matchCase ? s : s.toLowerCase()).toCharArray();
0516: firstCharWordPart = DocUtils
0517: .isIdentifierPart(doc, chars[0]);
0518: }
0519:
0520: public void reset() {
0521: super .reset();
0522: insideWord = false;
0523: wordFound = false;
0524: stringInd = 0;
0525: }
0526:
0527: public int find(int initOffset, CharSequence data) {
0528: int offset = 0;
0529: int limitPos = data.length();
0530: int limitOffset = limitPos - 1;
0531: while (offset >= 0 && offset < limitPos) {
0532: char ch = data.charAt(offset);
0533:
0534: if (!matchCase) {
0535: ch = Character.toLowerCase(ch);
0536: }
0537:
0538: // whole word already found but must verify next char
0539: if (wordFound) {
0540: if (DocUtils.isIdentifierPart(doc, ch)) { // word continues
0541: insideWord = firstCharWordPart;
0542: offset -= chars.length - 1;
0543: } else {
0544: int blkEnd = initOffset + offset;
0545: addBlock(blkEnd - chars.length, blkEnd);
0546: insideWord = false;
0547: offset++;
0548: }
0549: wordFound = false;
0550: stringInd = 0;
0551: continue;
0552: }
0553:
0554: if (stringInd == 0) { // special case for first char
0555: if (ch != chars[0] || insideWord) { // first char doesn't match
0556: insideWord = DocUtils.isIdentifierPart(doc, ch);
0557: offset++;
0558: } else { // first char matches
0559: stringInd = 1; // matched and not inside word
0560: if (chars.length == 1) {
0561: if (offset == limitOffset) {
0562: int blkStart = initOffset + offset;
0563: addBlock(blkStart, blkStart + 1);
0564: } else {
0565: wordFound = true;
0566: }
0567: }
0568: offset++;
0569: }
0570: } else { // already matched at least one char
0571: if (ch == chars[stringInd]) { // matches current char
0572: stringInd++;
0573: if (stringInd == chars.length) { // found whole string
0574: if (offset == limitOffset) {
0575: int blkEnd = initOffset + 1;
0576: addBlock(blkEnd - stringInd, blkEnd);
0577: } else {
0578: wordFound = true;
0579: }
0580: }
0581: offset++;
0582: } else { // current char doesn't match, stringInd > 0
0583: offset += 1 - stringInd;
0584: stringInd = 0;
0585: insideWord = firstCharWordPart;
0586: }
0587: }
0588:
0589: }
0590: return offset;
0591: }
0592:
0593: }
0594:
0595: /** String forward finder that creates position blocks */
0596: private static final class StringBlocksFinder extends
0597: AbstractBlocksFinder {
0598:
0599: char chars[];
0600:
0601: int stringInd;
0602:
0603: boolean matchCase;
0604:
0605: public StringBlocksFinder() {
0606: }
0607:
0608: public void setParams(String s, boolean matchCase) {
0609: this .matchCase = matchCase;
0610: chars = (matchCase ? s : s.toLowerCase()).toCharArray();
0611: }
0612:
0613: public void reset() {
0614: super .reset();
0615: stringInd = 0;
0616: }
0617:
0618: public int find(int initOffset, CharSequence data) {
0619: int offset = 0;
0620: int endPos = data.length();
0621: while (offset >= 0 && offset < endPos) {
0622: char ch = data.charAt(offset);
0623:
0624: if (!matchCase) {
0625: ch = Character.toLowerCase(ch);
0626: }
0627: if (ch == chars[stringInd]) {
0628:
0629: stringInd++;
0630: if (stringInd == chars.length) {
0631: int blkEnd = initOffset + offset + 1;
0632: addBlock(blkEnd - stringInd, blkEnd);
0633: stringInd = 0;
0634: }
0635: offset++;
0636: } else {
0637: offset += 1 - stringInd;
0638: stringInd = 0;
0639: }
0640:
0641: }
0642: return offset;
0643: }
0644:
0645: }
0646:
0647: private static final class WholeWordsBwdFinder extends
0648: GenericBwdFinder implements StringFinder {
0649:
0650: char chars[];
0651:
0652: int stringInd;
0653:
0654: boolean matchCase;
0655:
0656: boolean insideWord;
0657:
0658: boolean lastCharWordPart;
0659:
0660: boolean wordFound;
0661:
0662: int endInd;
0663:
0664: Document doc;
0665:
0666: public WholeWordsBwdFinder() {
0667: }
0668:
0669: public void setParams(Document doc, String s, boolean matchCase) {
0670: this .doc = doc;
0671: this .matchCase = matchCase;
0672: chars = (matchCase ? s : s.toLowerCase()).toCharArray();
0673: endInd = chars.length - 1;
0674: DocUtils.isIdentifierPart(doc, chars[endInd]);
0675: }
0676:
0677: public int getFoundLength() {
0678: return chars.length;
0679: }
0680:
0681: public void reset() {
0682: super .reset();
0683: insideWord = false;
0684: wordFound = false;
0685: stringInd = endInd;
0686: }
0687:
0688: protected int scan(char ch, boolean lastChar) {
0689: if (!matchCase) {
0690: ch = Character.toLowerCase(ch);
0691: }
0692:
0693: // whole word already found but must verify next char
0694: if (wordFound) {
0695: if (DocUtils.isIdentifierPart(doc, ch)) { // word continues
0696: wordFound = false;
0697: insideWord = lastCharWordPart;
0698: stringInd = endInd;
0699: return endInd;
0700: } else {
0701: found = true;
0702: return 1;
0703: }
0704: }
0705:
0706: if (stringInd == endInd) { // special case for last char
0707: if (ch != chars[endInd] || insideWord) { // first char doesn't match
0708: insideWord = DocUtils.isIdentifierPart(doc, ch);
0709: return -1;
0710: } else { // first char matches
0711: stringInd = endInd - 1; // matched and not inside word
0712: if (chars.length == 1) {
0713: if (lastChar) {
0714: found = true;
0715: return 0;
0716: } else {
0717: wordFound = true;
0718: return -1;
0719: }
0720: }
0721: return -1;
0722: }
0723: } else { // already matched at least one char
0724: if (ch == chars[stringInd]) { // matches current char
0725: stringInd--;
0726: if (stringInd == -1) { // found whole string
0727: if (lastChar) {
0728: found = true;
0729: return 0;
0730: } else {
0731: wordFound = true;
0732: return -1;
0733: }
0734: }
0735: return -1; // successfully matched char, go to next char
0736: } else { // current char doesn't match, stringInd > 0
0737: int back = chars.length - 2 - stringInd;
0738: stringInd = endInd;
0739: insideWord = lastCharWordPart;
0740: return back;
0741: }
0742: }
0743: }
0744: }
0745:
0746: /** Generic forward finder that simplifies the search process. */
0747: private static abstract class GenericFwdFinder extends
0748: AbstractFinder {
0749:
0750: public final int find(int initOffset, CharSequence chars) {
0751: int offset = 0;
0752: int limitPos = chars.length();
0753: int limitOffset = limitPos - 1;
0754: while (offset >= 0 && offset < limitPos) {
0755: offset += scan(chars.charAt(offset),
0756: (offset == limitOffset));
0757: if (found) {
0758: break;
0759: }
0760: }
0761: return offset;
0762: }
0763:
0764: /** This function decides if it found a desired string or not.
0765: * The function receives currently searched character and flag if it's
0766: * the last one that is searched or not.
0767: * @return if the function decides that
0768: * it found a desired string it sets <CODE>found = true</CODE> and returns
0769: * how many characters back the searched string begins in forward
0770: * direction (0 stands for current character).
0771: * For example if the function looks for word 'yes' and it gets
0772: * 's' as parameter it sets found = true and returns -2.
0773: * If the string is not yet found it returns how many characters it should go
0774: * in forward direction (in this case it would usually be 1).
0775: * The next searched character will be that one requested.
0776: */
0777: protected abstract int scan(char ch, boolean lastChar);
0778:
0779: }
0780:
0781: /** Generic backward finder that simplifies the search process. */
0782: private static abstract class GenericBwdFinder extends
0783: AbstractFinder {
0784:
0785: public final int find(int initOffset, CharSequence chars) {
0786: int offset = chars.length() - 1;
0787: int offset2;
0788: int limitPos = 0;
0789: int limitOffset = chars.length();
0790: while (offset >= 0 && offset < limitOffset) {
0791: offset += scan(chars.charAt(offset),
0792: (offset == limitOffset));
0793: if (found) {
0794: break;
0795: }
0796: }
0797: return offset;
0798: }
0799:
0800: /** This function decides if it found a desired string or not.
0801: * The function receives currently searched character and flag if it's
0802: * the last one that is searched or not.
0803: * @return if the function decides that
0804: * it found a desired string it sets <CODE>found = true</CODE> and returns
0805: * how many characters back the searched string begins in backward
0806: * direction (0 stands for current character). It is usually 0 as the
0807: * finder usually decides after the last required character but it's
0808: * not always the case e.g. for whole-words-only search it can be 1 or so.
0809: * If the string is not yet found it returns how many characters it should go
0810: * in backward direction (in this case it would usually be -1).
0811: * The next searched character will be that one requested.
0812: */
0813: protected abstract int scan(char ch, boolean lastChar);
0814:
0815: }
0816:
0817: private static final class WholeWordsFwdFinder extends
0818: GenericFwdFinder implements StringFinder {
0819:
0820: char chars[];
0821:
0822: int stringInd;
0823:
0824: boolean matchCase;
0825:
0826: Document doc;
0827:
0828: boolean insideWord;
0829:
0830: boolean firstCharWordPart;
0831:
0832: boolean wordFound;
0833:
0834: public WholeWordsFwdFinder() {
0835: }
0836:
0837: public void setParams(Document doc, String s, boolean matchCase) {
0838: this .doc = doc;
0839: this .matchCase = matchCase;
0840: chars = (matchCase ? s : s.toLowerCase()).toCharArray();
0841: firstCharWordPart = DocUtils
0842: .isIdentifierPart(doc, chars[0]);
0843: }
0844:
0845: public int getFoundLength() {
0846: return chars.length;
0847: }
0848:
0849: public void reset() {
0850: super .reset();
0851: insideWord = false;
0852: wordFound = false;
0853: stringInd = 0;
0854: }
0855:
0856: protected int scan(char ch, boolean lastChar) {
0857: if (!matchCase) {
0858: ch = Character.toLowerCase(ch);
0859: }
0860:
0861: // whole word already found but must verify next char
0862: if (wordFound) {
0863: if (DocUtils.isIdentifierPart(doc, ch)) { // word continues
0864: wordFound = false;
0865: insideWord = firstCharWordPart;
0866: stringInd = 0;
0867: return 1 - chars.length;
0868: } else {
0869: found = true;
0870: return -chars.length;
0871: }
0872: }
0873:
0874: if (stringInd == 0) { // special case for first char
0875: if (ch != chars[0] || insideWord) { // first char doesn't match
0876: insideWord = DocUtils.isIdentifierPart(doc, ch);
0877: return 1;
0878: } else { // first char matches
0879: stringInd = 1; // matched and not inside word
0880: if (chars.length == 1) {
0881: if (lastChar) {
0882: found = true;
0883: return 0;
0884: } else {
0885: wordFound = true;
0886: return 1;
0887: }
0888: }
0889: return 1;
0890: }
0891: } else { // already matched at least one char
0892: if (ch == chars[stringInd]) { // matches current char
0893: stringInd++;
0894: if (stringInd == chars.length) { // found whole string
0895: if (lastChar) {
0896: found = true;
0897: return 1 - chars.length; // how many chars back the string starts
0898: } else {
0899: wordFound = true;
0900: return 1;
0901: }
0902: }
0903: return 1; // successfully matched char, go to next char
0904: } else { // current char doesn't match, stringInd > 0
0905: int back = 1 - stringInd;
0906: stringInd = 0;
0907: insideWord = firstCharWordPart;
0908: return back; // go back to search from the next to first char
0909: }
0910: }
0911: }
0912:
0913: }
0914:
0915: private static class StringBwdFinder extends GenericBwdFinder
0916: implements StringFinder {
0917:
0918: char chars[];
0919:
0920: int stringInd;
0921:
0922: boolean matchCase;
0923:
0924: int endInd;
0925:
0926: public StringBwdFinder() {
0927: }
0928:
0929: public void setParams(String s, boolean matchCase) {
0930: this .matchCase = matchCase;
0931: chars = (matchCase ? s : s.toLowerCase()).toCharArray();
0932: endInd = chars.length - 1;
0933: }
0934:
0935: public int getFoundLength() {
0936: return chars.length;
0937: }
0938:
0939: public void reset() {
0940: super .reset();
0941: stringInd = endInd;
0942: }
0943:
0944: protected int scan(char ch, boolean lastChar) {
0945: if (!matchCase) {
0946: ch = Character.toLowerCase(ch);
0947: }
0948: if (ch == chars[stringInd]) {
0949: stringInd--;
0950: if (stringInd == -1) {
0951: found = true;
0952: return 0;
0953: }
0954: return -1;
0955: } else {
0956: if (stringInd == endInd) {
0957: return -1;
0958: } else {
0959: int back = chars.length - 2 - stringInd;
0960: stringInd = endInd;
0961: return back;
0962: }
0963: }
0964: }
0965:
0966: }
0967:
0968: private static final class StringFwdFinder extends GenericFwdFinder
0969: implements StringFinder {
0970:
0971: char chars[];
0972:
0973: int stringInd;
0974:
0975: boolean matchCase;
0976:
0977: public StringFwdFinder() {
0978: }
0979:
0980: public void setParams(String s, boolean matchCase) {
0981: this .matchCase = matchCase;
0982: chars = (matchCase ? s : s.toLowerCase()).toCharArray();
0983: }
0984:
0985: public int getFoundLength() {
0986: return chars.length;
0987: }
0988:
0989: public void reset() {
0990: super .reset();
0991: stringInd = 0;
0992: }
0993:
0994: protected int scan(char ch, boolean lastChar) {
0995: if (!matchCase) {
0996: ch = Character.toLowerCase(ch);
0997: }
0998: if (ch == chars[stringInd]) {
0999: stringInd++;
1000: if (stringInd == chars.length) { // found whole string
1001: found = true;
1002: return 1 - stringInd; // how many chars back the string starts
1003: }
1004: return 1; // successfully matched char, go to next char
1005: } else {
1006: if (stringInd == 0) {
1007: return 1;
1008: } else {
1009: int back = 1 - stringInd;
1010: stringInd = 0;
1011: return back;
1012: }
1013: }
1014: }
1015:
1016: }
1017:
1018: private abstract static class RegExpFinder extends AbstractFinder
1019: implements StringFinder {
1020: public abstract Matcher getMatcher();
1021: }
1022:
1023: // ----------------- regexp ----------------------
1024: private static class RegExpBwdFinder extends RegExpFinder {
1025:
1026: boolean matchCase;
1027: Pattern pattern;
1028: int length = 0;
1029: Matcher matcher;
1030:
1031: public RegExpBwdFinder() {
1032: }
1033:
1034: public Matcher getMatcher() {
1035: return matcher;
1036: }
1037:
1038: public void setParams(Pattern pattern, boolean matchCase) {
1039: this .matchCase = matchCase;
1040: this .pattern = pattern;
1041: }
1042:
1043: public int getFoundLength() {
1044: return length;
1045: }
1046:
1047: public void reset() {
1048: super .reset();
1049: length = 0;
1050: }
1051:
1052: private int lineFind(int lineStart, int lineEnd,
1053: CharSequence chars) {
1054: matcher = pattern.matcher(chars.subSequence(lineStart,
1055: lineEnd));
1056: int ret = -1;
1057: while (matcher.find()) {
1058: int start = matcher.start();
1059: int end = matcher.end();
1060: length = end - start;
1061: if (length <= 0) {
1062: found = false;
1063: return -1;
1064: }
1065: ret = start;
1066: }
1067: return ret;
1068: }
1069:
1070: public int find(int initOffset, CharSequence chars) {
1071: char ch;
1072:
1073: int charsEnd = chars.length() - 1;
1074: int lineEnd = charsEnd;
1075: int lineStart = charsEnd;
1076: for (int i = charsEnd; i >= 0; i--) {
1077: ch = chars.charAt(i);
1078: if (ch == '\n' || i == 0) {
1079: int retFind = lineFind(lineStart
1080: + ((i == 0) ? 0 : 1), lineEnd + 1, chars);
1081: if (retFind != -1) {
1082: found = true;
1083: return i + retFind + ((i == 0) ? 0 : 1);
1084: }
1085: lineStart--;
1086: lineEnd = lineStart;
1087: } else {
1088: lineStart--;
1089: }
1090: }
1091: return -1;
1092: }
1093:
1094: }
1095:
1096: private static final class RegExpFwdFinder extends RegExpFinder {
1097:
1098: Pattern pattern;
1099: boolean matchCase;
1100: int length = 0;
1101: Matcher matcher;
1102:
1103: public RegExpFwdFinder() {
1104: }
1105:
1106: public Matcher getMatcher() {
1107: return matcher;
1108: }
1109:
1110: public void setParams(Pattern pattern, boolean matchCase) {
1111: this .matchCase = matchCase;
1112: this .pattern = pattern;
1113: }
1114:
1115: public int getFoundLength() {
1116: return length;
1117: }
1118:
1119: public void reset() {
1120: super .reset();
1121: length = 0;
1122: }
1123:
1124: public int find(int initOffset, CharSequence chars) {
1125: matcher = pattern.matcher(chars);
1126: if (matcher.find()) {
1127: found = true;
1128: int start = matcher.start();
1129: int end = matcher.end();
1130: length = end - start;
1131: if (length <= 0) {
1132: found = false;
1133: return -1;
1134: }
1135: return start;
1136: } else {
1137: return -1;
1138: }
1139:
1140: }
1141: }
1142:
1143: /** String forward finder that creates position blocks */
1144: private static final class RegExpBlocksFinder extends
1145: AbstractBlocksFinder {
1146:
1147: Pattern pattern;
1148:
1149: int stringInd;
1150:
1151: boolean matchCase;
1152:
1153: public RegExpBlocksFinder() {
1154: }
1155:
1156: public void setParams(Pattern pattern, boolean matchCase) {
1157: this .pattern = pattern;
1158: this .matchCase = matchCase;
1159: }
1160:
1161: public void reset() {
1162: super .reset();
1163: stringInd = 0;
1164: }
1165:
1166: public int find(int initOffset, CharSequence data) {
1167: Matcher matcher = pattern.matcher(data);
1168: int ret = 0;
1169: while (matcher.find()) {
1170: int start = initOffset + matcher.start();
1171: int end = initOffset + matcher.end();
1172: addBlock(start, end);
1173: ret = start;
1174: }
1175: return ret;
1176: }
1177:
1178: }
1179:
1180: private static class PatternCache {
1181:
1182: private static String cache_str;
1183: private static boolean cache_matchCase;
1184: private static Pattern cache_pattern;
1185:
1186: private PatternCache() {
1187: }
1188:
1189: public static void putPattern(String str, boolean matchCase,
1190: Pattern pattern) {
1191: cache_str = str;
1192: cache_matchCase = matchCase;
1193: cache_pattern = pattern;
1194: }
1195:
1196: public static Pattern getPattern(String str, boolean matchCase) {
1197: if (str == null)
1198: return null;
1199: if (str.equals(cache_str) && matchCase == cache_matchCase) {
1200: return cache_pattern;
1201: }
1202: return null;
1203: }
1204:
1205: public static void clear() {
1206: cache_str = null;
1207: cache_matchCase = false;
1208: cache_pattern = null;
1209: }
1210: }
1211:
1212: public static class FindReplaceResult {
1213: private int[] positions;
1214: private String replacedString;
1215:
1216: public FindReplaceResult(int[] positions, String replacedString) {
1217: this .positions = positions;
1218: this .replacedString = replacedString;
1219: }
1220:
1221: public String getReplacedString() {
1222: return replacedString;
1223: }
1224:
1225: public int[] getFoundPositions() {
1226: return positions;
1227: }
1228: }
1229:
1230: }
|