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.editor.ext;
043:
044: import java.util.List;
045: import java.util.Iterator;
046: import java.awt.Color;
047: import java.awt.Component;
048: import javax.swing.JLabel;
049: import javax.swing.JList;
050: import javax.swing.text.Document;
051: import javax.swing.text.JTextComponent;
052: import javax.swing.text.BadLocationException;
053: import org.netbeans.editor.SyntaxSupport;
054: import org.netbeans.editor.BaseDocument;
055: import org.netbeans.lib.editor.util.CharSequenceUtilities;
056: import org.netbeans.lib.editor.util.swing.DocumentUtilities;
057:
058: /**
059: * Code completion querying SPI and support.
060: *
061: * @author Miloslav Metelka
062: * @version 1.01
063: */
064:
065: public interface CompletionQuery {
066:
067: /** Perform the query on the given component. The query usually
068: * gets the component's document, the caret position and searches back
069: * to find the last command start. Then it inspects the text up to the caret
070: * position and returns the result.
071: * <p>Implementations must be thread safe (also reentrant) because it can be
072: * called speculatively from multiple threads. This requirement can be removed in future SPI
073: * by passing additional flag marking speculative query. Skeletal implementation
074: * could handle multithreading based on the flag.
075: * @param component the component to use in this query.
076: * @param offset position in the component's document to which the query will
077: * be performed. Usually it's a caret position.
078: * @param support syntax-support that will be used during resolving of the query.
079: * @return result of the query or null if there's no result.
080: */
081: public Result query(JTextComponent component, int offset,
082: SyntaxSupport support);
083:
084: /**
085: * Marker interface that should implement all providers that are
086: * compatible with #13768 semantics. It requires thread safe and reentrant
087: * completion query provider implementation. It's performance gain
088: * to implement as it allows asynchronous speculative queries.
089: *
090: * @deprecated It is a workaround. It's suggested that providers
091: * should wait for new completion query SPI that should better
092: * support speculative queries, partial results, result
093: * cancellation and result narrowing. Implement only if it's simple.
094: *
095: * @since CompletionQuery version 1.01
096: */
097: public interface SupportsSpeculativeInvocation {
098: // marker interface
099: }
100:
101: /** Result of the query or expression evaluation. Simply said it consists
102: * of the list of the data and title and an internal information about
103: * how to substitute the text.
104: */
105: public interface Result {
106:
107: /** Get the list with the items satisfying the query. The list
108: * must always be non-null. If there are no data it will have a zero size.
109: * @return List of objects implementing ResultItem.
110: */
111: public List getData();
112:
113: /** Get the title describing the result or null if there's no title. */
114: public String getTitle();
115:
116: /** Substitute the text in the document if the user picks
117: * the item from the data with the given index either by
118: * pressing ENTER or doubleclicking the item by mouse.
119: * @param dataIndex current selected item index in the current data list.
120: * It can be used for making the substitution.
121: * @param shift indicates request for some kind of different behaviour,
122: * means that e.g. user hold shift while pressing ENTER.
123: * @return whether the text was substituted or not
124: */
125: public boolean substituteText(int dataIndex, boolean shift);
126:
127: /** Substitute the text that is common for all the data entries.
128: * This is used to update the document with
129: * the common text when the user presses the TAB key.
130: * @param dataIndex current selected item index in the current data list.
131: * Although normally it shouldn't be necessary for making
132: * the substitution, the completion implementations
133: * can use it for customized behavior.
134: * @return whether the text was substituted or not
135: */
136: public boolean substituteCommonText(int dataIndex);
137:
138: }
139:
140: /**
141: * The very basic funztionality of Result is implemented by this class,
142: * but parts general enough to not need to be overriden.
143: */
144: public static abstract class AbstractResult implements Result {
145:
146: /** The List of the ResultItem instances - the content of the result */
147: private List data;
148:
149: /** The title of the result */
150: private String title;
151:
152: public AbstractResult(List data, String title) {
153: this .data = data;
154: this .title = title;
155: }
156:
157: public List getData() {
158: return data;
159: }
160:
161: public String getTitle() {
162: return title;
163: }
164:
165: }
166:
167: /** Full implementation of Result, managing substitution of the text and
168: * finding and substituting common prefix of items
169: */
170: public static class DefaultResult extends AbstractResult {
171:
172: private JTextComponent component;
173: private int offset;
174: private int len;
175:
176: /** Constructor for DefaultResult
177: * @param component the JTextComponent the result is tightened with,
178: * used for operations on its Document, caret, selection and so.
179: * @param title the title displayed in header of completion window
180: * @param data the list of ResultItem instances to be displayed in
181: * completion window, may be null.
182: * @param the offset in the document corresponding to the start
183: * of the text occassionally replaced by the result.
184: * @param the length of the text to be replaced.
185: */
186: public DefaultResult(JTextComponent component, String title,
187: List data, int offset, int len) {
188: super (data, title);
189: this .component = component;
190: this .offset = offset;
191: this .len = len;
192: }
193:
194: /** Internal method used to find longest common prefix of two Strings.
195: * it is made private, because I'm going to change its interface
196: * for better performance.
197: */
198: private int getCommonPrefixLength(char[] commonPrefix, int len,
199: String s) {
200: char[] c = s.toCharArray();
201: int i = 0;
202: if (len > c.length)
203: len = c.length;
204: for (; i < len; i++) {
205: if (commonPrefix[i] != c[i])
206: break;
207: }
208: return i;
209: }
210:
211: /** Update the text in response to pressing TAB key. Searches through
212: * all items of this result looking for longest common prefix and then
213: * calls the substitution method on selected item providing it with
214: * the length of common part.
215: * @return whether the text was successfully updated
216: */
217: public boolean substituteCommonText(int dataIndex) {
218: List data = getData();
219: if (data.size() == 0)
220: return false;
221:
222: Iterator i = data.iterator();
223: char[] commonPrefix = ((CompletionQuery.ResultItem) i
224: .next()).getItemText().toCharArray();
225: int commonLength = commonPrefix.length;
226:
227: for (; i.hasNext();) {
228: String second = ((CompletionQuery.ResultItem) i.next())
229: .getItemText();
230: commonLength = getCommonPrefixLength(commonPrefix,
231: commonLength, second);
232: }
233: CompletionQuery.ResultItem actData = (CompletionQuery.ResultItem) data
234: .get(dataIndex);
235: return actData.substituteCommonText(component, offset, len,
236: commonLength);
237: }
238:
239: /** Update the text in response to pressing ENTER.
240: * @return whether the text was successfully updated
241: */
242: public boolean substituteText(int dataIndex, boolean shift) {
243: Object actData = getData().get(dataIndex);
244: return ((CompletionQuery.ResultItem) actData)
245: .substituteText(component, offset, len, shift);
246: }
247: }
248:
249: /** An interface used as an item of List returned by CompletionQuery.Result.getData()
250: * Such items are then able to their part in Completion process themselves
251: */
252: public static interface ResultItem {
253: /** Update the text in response to pressing TAB key (or any key mapped to
254: * this function) on this element
255: * @param c the text component to operate on, enables implementation to
256: * do things like movement of caret.
257: * @param offset the offset where the item should be placed
258: * @param len the length of recognized text which should be replaced
259: * @param subLen the length of common part - the length of text that should
260: * be inserted after removal of recognized text
261: * @return whether the text was successfully updated
262: */
263: public boolean substituteCommonText(JTextComponent c,
264: int offset, int len, int subLen);
265:
266: /** Update the text in response to pressing ENTER on this element.
267: * @param c the text component to operate on, enables implementation to
268: * do things like movement of caret.
269: * @param offset the offset where the item should be placed
270: * @param len the length of recognized text which should be replaced
271: * @param shift the flag that instructs completion to behave somehow
272: * differently - enables more kinds of invocation of substituteText
273: * @return whether the text was successfully updated
274: */
275: public boolean substituteText(JTextComponent c, int offset,
276: int len, boolean shift);
277:
278: /** Says what text would this Element use if substituteText is called.
279: * @return the substitution text, usable e.g. for finding common text/its' length
280: */
281: public String getItemText();
282:
283: /** Prepare proper component for painting value of <CODE>this</CODE>.
284: * @param JList the list this item will be drawn into, usefull e.g. for
285: * obtaining preferred colors.
286: * @param isSelected tells if this item is just selected, for using
287: * proper color scheme.
288: * @param cellHasFocus tells it this item is just focused.
289: * @return the component usable for painting this value
290: */
291: public Component getPaintComponent(JList list,
292: boolean isSelected, boolean cellHasFocus);
293: }
294:
295: /**
296: * An interface allowing to obtain
297: * the object carrying the actual data associated with the result item.
298: */
299: public static interface ResultItemAssociatedObject {
300:
301: /**
302: * Get the object carrying the actual data associated with the result item
303: */
304: Object getAssociatedObject();
305:
306: }
307:
308: /** A class providing generic, nearly full implementation of ResultItem
309: */
310: public abstract static class AbstractResultItem implements
311: CompletionQuery.ResultItem {
312: /* The text this item would expand to */
313: protected String text;
314:
315: /** Create new ResultItem for given text, should be used in subclass constructors
316: */
317: public AbstractResultItem(String text) {
318: this .text = text;
319: }
320:
321: /** Generic implementation, behaves just as described in specification
322: * in substituteCommonText() - removes <CODE>len</CODE>
323: * characters at <CODE>offset</CODE> out of document and then inserts
324: * <CODE>subLen<CODE> characters from the <CODE>text</CODE>
325: */
326: public boolean substituteCommonText(JTextComponent c,
327: int offset, int len, int subLen) {
328: BaseDocument doc = (BaseDocument) c.getDocument();
329: try {
330: doc.atomicLock();
331: try {
332: doc.remove(offset, len);
333: doc.insertString(offset, text.substring(0, subLen),
334: null);
335: } finally {
336: doc.atomicUnlock();
337: }
338: } catch (BadLocationException exc) {
339: return false; //not sucessfull
340: }
341: return true;
342: }
343:
344: /** Generic implementation, behaves just as described in specification
345: * in substituteText() - removes <CODE>len</CODE> characters
346: * at <CODE>offset</CODE> out of document and then inserts
347: * whole <CODE>text</CODE>. Ignores <CODE>shift</CODE> argument.
348: */
349: public boolean substituteText(JTextComponent c, int offset,
350: int len, boolean shift) {
351: BaseDocument doc = (BaseDocument) c.getDocument();
352: try {
353: doc.atomicLock();
354: try {
355: CharSequence textToReplace = DocumentUtilities
356: .getText(doc, offset, len);
357: if (CharSequenceUtilities.textEquals(text,
358: textToReplace))
359: return false;
360: doc.remove(offset, len);
361: doc.insertString(offset, text, null);
362: } finally {
363: doc.atomicUnlock();
364: }
365: } catch (BadLocationException exc) {
366: return false; //not sucessfull
367: }
368: return true;
369: }
370:
371: /** @return the text this item would expand to.
372: */
373: public String getItemText() {
374: return text;
375: }
376:
377: }
378:
379: public static class DefaultResultItem extends
380: CompletionQuery.AbstractResultItem {
381: /** The cache for component used for painting value of <CODE>this</CODE>
382: * this component is reused, on every call to getPaintComponent it is
383: * set up and then painted. By default, this component is hold opaque.
384: */
385: static JLabel rubberStamp = new JLabel();
386:
387: static {
388: rubberStamp.setOpaque(true);
389: }
390:
391: /** Color used for painting text of non-selected item */
392: protected Color foreColor;
393:
394: public DefaultResultItem(String text, Color foreColor) {
395: super (text);
396: this .foreColor = foreColor;
397: }
398:
399: public Component getPaintComponent(JList list,
400: boolean isSelected, boolean cellHasFocus) {
401: rubberStamp.setText(" " + text); // NOI18N
402: if (isSelected) {
403: rubberStamp
404: .setBackground(list.getSelectionBackground());
405: rubberStamp
406: .setForeground(list.getSelectionForeground());
407: } else {
408: rubberStamp.setBackground(list.getBackground());
409: rubberStamp.setForeground(foreColor);
410: }
411: return rubberStamp;
412: }
413: }
414:
415: }
|