001: /*
002: * SearchAndReplace.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.gui.editor;
013:
014: import java.awt.Container;
015: import java.awt.Toolkit;
016: import java.util.regex.Matcher;
017: import java.util.regex.Pattern;
018: import workbench.gui.WbSwingUtilities;
019: import workbench.gui.actions.FindAction;
020: import workbench.gui.actions.FindAgainAction;
021: import workbench.gui.actions.ReplaceAction;
022: import workbench.gui.components.ReplacePanel;
023: import workbench.gui.components.SearchCriteriaPanel;
024: import workbench.interfaces.Replaceable;
025: import workbench.interfaces.Searchable;
026: import workbench.interfaces.TextContainer;
027: import workbench.log.LogMgr;
028: import workbench.resource.ResourceMgr;
029: import workbench.util.ExceptionUtil;
030: import workbench.util.StringUtil;
031:
032: /**
033: * This class offeres Search & Replace for a TextContainer
034: *
035: * @author support@sql-workbench.net
036: */
037: public class SearchAndReplace implements Replaceable, Searchable {
038: private String lastSearchExpression;
039: private String lastSearchCriteria;
040: private Pattern lastSearchPattern;
041: private int lastSearchPos;
042: private ReplacePanel replacePanel;
043:
044: private TextContainer editor;
045: private Container parent;
046:
047: private FindAction findAction;
048: private FindAgainAction findAgainAction;
049: private ReplaceAction replaceAction;
050:
051: /**
052: * Create a new SearchAndReplace support.
053: * @param parentContainer the parent of the textcontainer, needed for displaying dialogs
054: * @param text the container holding the text
055: */
056: public SearchAndReplace(Container parentContainer,
057: TextContainer text) {
058: this .editor = text;
059: this .parent = parentContainer;
060: this .findAction = new FindAction(this );
061: this .findAction.setEnabled(true);
062: this .findAgainAction = new FindAgainAction(this );
063: this .findAgainAction.setEnabled(false);
064: this .replaceAction = new ReplaceAction(this );
065: this .replaceAction.setEnabled(true);
066: }
067:
068: public ReplaceAction getReplaceAction() {
069: return this .replaceAction;
070: }
071:
072: public FindAgainAction getFindAgainAction() {
073: return this .findAgainAction;
074: }
075:
076: public FindAction getFindAction() {
077: return this .findAction;
078: }
079:
080: private String getText() {
081: return editor.getText();
082: }
083:
084: private String getSelectedText() {
085: return editor.getSelectedText();
086: }
087:
088: private int getCaretPosition() {
089: return editor.getCaretPosition();
090: }
091:
092: /**
093: * Show the find dialog and start searching.
094: * @return -1 if nothing was found,
095: * the position of the found text otherwise
096: */
097: public int find() {
098: boolean showDialog = true;
099: String crit = this .getSelectedText();
100:
101: // Do not use multi-line selections as the default search criteria
102: if (crit != null && crit.indexOf('\n') > -1)
103: crit = null;
104:
105: if (crit == null)
106: crit = this .lastSearchCriteria;
107: SearchCriteriaPanel p = new SearchCriteriaPanel(crit);
108:
109: int pos = -1;
110: while (showDialog) {
111: boolean doFind = p.showFindDialog(this .parent);
112: if (!doFind)
113: return -1;
114: String criteria = p.getCriteria();
115: boolean ignoreCase = p.getIgnoreCase();
116: boolean wholeWord = p.getWholeWordOnly();
117: boolean useRegex = p.getUseRegex();
118: try {
119: this .lastSearchCriteria = criteria;
120: this .findAgainAction.setEnabled(false);
121: pos = this .findText(criteria, ignoreCase, wholeWord,
122: useRegex);
123: showDialog = false;
124: this .findAgainAction.setEnabled(pos > -1);
125: } catch (Exception e) {
126: pos = -1;
127: WbSwingUtilities.showErrorMessage(this .parent,
128: ExceptionUtil.getDisplay(e));
129: showDialog = true;
130: }
131: }
132: return pos;
133: }
134:
135: public int findNext() {
136: if (this .lastSearchPattern == null)
137: return -1;
138: if (this .lastSearchPos == -1)
139: return -1;
140:
141: Matcher m = this .lastSearchPattern.matcher(this .getText());
142:
143: if (m.find(this .getCaretPosition() + 1)) {
144: this .lastSearchPos = m.start();
145: int end = m.end();
146: this .editor.select(this .lastSearchPos, end);
147: } else {
148: this .lastSearchPos = -1;
149: Toolkit.getDefaultToolkit().beep();
150: String msg = ResourceMgr
151: .getString("MsgEditorCriteriaNotFound");
152: msg = StringUtil.replace(msg, "%value%",
153: this .lastSearchExpression);
154: WbSwingUtilities.showMessage(this .parent, msg);
155: }
156: return this .lastSearchPos;
157: }
158:
159: public int findFirst(String aValue, boolean ignoreCase,
160: boolean wholeWord, boolean useRegex) {
161: int pos = this
162: .findText(aValue, ignoreCase, wholeWord, useRegex);
163: return pos;
164: }
165:
166: public void replace() {
167: if (this .replacePanel == null) {
168: this .replacePanel = new ReplacePanel(this );
169: }
170: this .replacePanel.showReplaceDialog(this .parent, this .editor
171: .getSelectedText());
172: }
173:
174: /**
175: * Find and replace the next occurance of the current search string
176: */
177: public boolean replaceNext(String aReplacement, boolean useRegex) {
178: try {
179: int pos = this .findNext();
180: if (pos > -1) {
181: String text = this .getSelectedText();
182: Matcher m = this .lastSearchPattern.matcher(text);
183: String newText = m
184: .replaceAll(fixSpecialReplacementChars(
185: aReplacement, useRegex));
186: this .editor.setSelectedText(newText);
187: }
188:
189: return (pos > -1);
190: } catch (Exception e) {
191: LogMgr.logError("SearchAndReplace.replaceNext()",
192: "Error replacing value", e);
193: WbSwingUtilities.showErrorMessage(e.getMessage());
194: return false;
195: }
196: }
197:
198: public boolean isTextSelected() {
199: int selStart = this .editor.getSelectionStart();
200: int selEnd = this .editor.getSelectionEnd();
201: return (selStart > -1 && selEnd > selStart);
202: }
203:
204: /**
205: * Replace special characters in the input string so that it can be used
206: * as a replacement using regular expressions.
207: */
208: public static final String fixSpecialReplacementChars(String input,
209: boolean useRegex) {
210: if (!useRegex) {
211: return StringUtil.quoteRegexMeta(input);
212: }
213:
214: String fixed = input.replaceAll("\\\\n", "\n");
215: fixed = fixed.replaceAll("\\\\r", "\r");
216: fixed = fixed.replaceAll("\\\\t", "\t");
217: fixed = StringUtil.quoteRegexMeta(fixed);
218: return fixed;
219: }
220:
221: public int replaceAll(String value, String replacement,
222: boolean selectedText, boolean ignoreCase,
223: boolean wholeWord, boolean useRegex) {
224: String old = null;
225: if (selectedText) {
226: old = this .getSelectedText();
227: } else {
228: old = this .getText();
229: }
230: int cursor = this .getCaretPosition();
231: int selStart = this .editor.getSelectionStart();
232: int selEnd = this .editor.getSelectionEnd();
233: int newLen = -1;
234: String regex = getSearchExpression(value, ignoreCase,
235: wholeWord, useRegex);
236: replacement = fixSpecialReplacementChars(replacement, useRegex);
237:
238: Pattern p = Pattern.compile(regex);
239: Matcher m = p.matcher(old);
240: String newText = m.replaceAll(replacement);
241:
242: if (selectedText) {
243: this .editor.setSelectedText(newText);
244: newLen = this .getText().length();
245: } else {
246: this .editor.setText(newText);
247: newLen = this .getText().length();
248: selStart = -1;
249: selEnd = -1;
250: }
251: if (cursor < newLen) {
252: this .editor.setCaretPosition(cursor);
253: }
254: if (selStart > -1 && selEnd > selStart && selStart < newLen
255: && selEnd < newLen) {
256: this .editor.select(selStart, selEnd);
257: }
258: return 0;
259: }
260:
261: public boolean replaceCurrent(String replacement, boolean useRegex) {
262: if (this .searchPatternMatchesSelectedText()) {
263: try {
264: Matcher m = this .lastSearchPattern.matcher(this
265: .getSelectedText());
266: String newText = m
267: .replaceAll(fixSpecialReplacementChars(
268: replacement, useRegex));
269: this .editor.setSelectedText(newText);
270: return true;
271: } catch (Exception e) {
272: LogMgr.logError("SearchAndReplace.replaceCurrent()",
273: "Error replacing value", e);
274: WbSwingUtilities.showErrorMessage(e.getMessage());
275: return false;
276: }
277: } else {
278: return replaceNext(replacement, useRegex);
279: }
280: }
281:
282: public int findText(String anExpression, boolean ignoreCase) {
283: return this .findText(anExpression, ignoreCase, false, true);
284: }
285:
286: public static final String getSearchExpression(String anExpression,
287: boolean ignoreCase, boolean wholeWord, boolean useRegex) {
288: StringBuilder result = new StringBuilder(
289: anExpression.length() + 10);
290:
291: final String ignoreModifier = "(?i)";
292:
293: if (ignoreCase) {
294: result.append(ignoreModifier);
295: }
296:
297: if (!useRegex) {
298: result.append('(');
299: result.append(StringUtil.quoteRegexMeta(anExpression));
300: result.append(')');
301: } else {
302: result.append(anExpression);
303: }
304:
305: if (wholeWord) {
306: char c = anExpression.charAt(0);
307: // word boundary dos not work if the expression starts with
308: // a special Regex character. So in that case, we'll just ignore it
309: if (StringUtil.REGEX_SPECIAL_CHARS.indexOf(c) == -1) {
310: if (ignoreCase)
311: result.insert(ignoreModifier.length(), "\\b");
312: else
313: result.insert(0, "\\b");
314: }
315: c = anExpression.charAt(anExpression.length() - 1);
316: if (StringUtil.REGEX_SPECIAL_CHARS.indexOf(c) == -1) {
317: result.append("\\b");
318: }
319: }
320: return result.toString();
321: }
322:
323: public boolean isCurrentSearchCriteria(String aValue,
324: boolean ignoreCase, boolean wholeWord, boolean useRegex) {
325: if (this .lastSearchExpression == null)
326: return false;
327: if (aValue == null)
328: return false;
329: String regex = getSearchExpression(aValue, ignoreCase,
330: wholeWord, useRegex);
331: return regex.equals(this .lastSearchExpression);
332: }
333:
334: public int findText(String anExpression, boolean ignoreCase,
335: boolean wholeWord, boolean useRegex) {
336: String regex = getSearchExpression(anExpression, ignoreCase,
337: wholeWord, useRegex);
338:
339: int end = -1;
340: this .lastSearchPattern = Pattern.compile(regex);
341: this .lastSearchExpression = anExpression;
342: Matcher m = this .lastSearchPattern.matcher(this .getText());
343:
344: int startPos = this .isTextSelected() ? this .editor
345: .getSelectionStart() : this .getCaretPosition();
346: if (m.find(startPos)) {
347: this .lastSearchPos = m.start();
348: end = m.end();
349: this .editor.select(this .lastSearchPos, end);
350: } else {
351: this .lastSearchPos = -1;
352: Toolkit.getDefaultToolkit().beep();
353: String msg = ResourceMgr
354: .getString("MsgEditorCriteriaNotFound");
355: msg = StringUtil.replace(msg, "%value%", anExpression);
356: WbSwingUtilities.showMessage(this .parent, msg);
357: }
358: return this .lastSearchPos;
359: }
360:
361: public boolean searchPatternMatchesSelectedText() {
362: if (this .lastSearchPattern == null)
363: return false;
364: Matcher m = this.lastSearchPattern.matcher(this
365: .getSelectedText());
366: return m.matches();
367: }
368:
369: }
|