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.spring.beans.completion;
043:
044: import java.util.ArrayList;
045: import java.util.List;
046: import javax.swing.text.Document;
047: import org.netbeans.editor.BaseDocument;
048: import org.netbeans.editor.TokenItem;
049: import org.netbeans.modules.spring.beans.editor.DocumentContext;
050: import org.netbeans.modules.spring.beans.editor.EditorContextFactory;
051: import org.netbeans.modules.xml.text.api.XMLDefaultTokenContext;
052: import org.netbeans.modules.xml.text.syntax.SyntaxElement;
053: import org.netbeans.modules.xml.text.syntax.XMLSyntaxSupport;
054: import org.netbeans.modules.xml.text.syntax.dom.EmptyTag;
055: import org.netbeans.modules.xml.text.syntax.dom.EndTag;
056: import org.netbeans.modules.xml.text.syntax.dom.StartTag;
057: import org.netbeans.modules.xml.text.syntax.dom.Tag;
058: import org.w3c.dom.Node;
059:
060: /**
061: * Tracks context information for a code completion scenario
062: *
063: * @author Rohan Ranade (Rohan.Ranade@Sun.COM)
064: */
065: public class CompletionContext {
066: private ArrayList<String> existingAttributes;
067:
068: public static enum CompletionType {
069: TAG, VALUE, ATTRIBUTE, ATTRIBUTE_VALUE, NONE
070: };
071:
072: private CompletionType completionType = CompletionType.NONE;
073: private Document doc;
074: private int caretOffset;
075: private DocumentContext documentContext;
076: private String typedChars = "";
077: private char lastTypedChar;
078: private XMLSyntaxSupport support;
079:
080: public CompletionContext(Document doc, int caretOffset) {
081: this .doc = doc;
082: this .caretOffset = caretOffset;
083: this .support = (XMLSyntaxSupport) ((BaseDocument) doc)
084: .getSyntaxSupport();
085: this .documentContext = EditorContextFactory.getDocumentContext(
086: doc, caretOffset);
087: this .lastTypedChar = support.lastTypedChar();
088: initContext();
089: }
090:
091: private void initContext() {
092: TokenItem token = documentContext.getCurrentToken();
093: boolean tokenBoundary = (token.getOffset() == caretOffset)
094: || ((token.getOffset() + token.getImage().length()) == caretOffset);
095:
096: int id = token.getTokenID().getNumericID();
097: SyntaxElement element = documentContext.getCurrentElement();
098: switch (id) {
099: //user enters < character
100: case XMLDefaultTokenContext.TEXT_ID:
101: String chars = token.getImage().trim();
102: if (chars != null
103: && chars.equals("")
104: && token.getPrevious().getImage().trim().equals(
105: "/>")) { // NOI18N
106: completionType = CompletionType.NONE;
107: break;
108: }
109: if (chars != null
110: && chars.equals("")
111: && token.getPrevious().getImage().trim()
112: .equals(">")) { // NOI18N
113: completionType = CompletionType.VALUE;
114: break;
115: }
116: if (chars != null
117: && !chars.equals("<")
118: && token.getPrevious().getImage().trim()
119: .equals(">")) { // NOI18N
120: completionType = CompletionType.NONE;
121: break;
122: }
123: if (chars != null && chars.startsWith("<")) { // NOI18N
124: typedChars = chars.substring(1);
125: }
126: completionType = CompletionType.TAG;
127: break;
128:
129: //start tag of an element
130: case XMLDefaultTokenContext.TAG_ID:
131: if (element instanceof EndTag) {
132: completionType = CompletionType.NONE;
133: break;
134: }
135: if (element instanceof EmptyTag) {
136: if (token != null
137: && token.getImage().trim().equals("/>")) {
138: completionType = CompletionType.NONE;
139: break;
140: }
141: EmptyTag tag = (EmptyTag) element;
142: if (element.getElementOffset() + 1 == this .caretOffset) {
143: completionType = CompletionType.TAG;
144: break;
145: }
146: if (caretOffset > element.getElementOffset() + 1
147: && caretOffset <= element.getElementOffset()
148: + 1 + tag.getTagName().length()) {
149: completionType = CompletionType.TAG;
150: typedChars = tag.getTagName();
151: break;
152: }
153: completionType = CompletionType.ATTRIBUTE;
154: break;
155: }
156:
157: if (element instanceof StartTag) {
158: if (token != null
159: && token.getImage().trim().equals(">")) {
160: completionType = CompletionType.NONE;
161: break;
162: }
163: if (element.getElementOffset() + 1 != this .caretOffset) {
164: StartTag tag = (StartTag) element;
165: typedChars = tag.getTagName();
166: }
167: }
168: if (lastTypedChar == '>') {
169: completionType = CompletionType.VALUE;
170: break;
171: }
172: completionType = CompletionType.TAG;
173: break;
174:
175: //user enters an attribute name
176: case XMLDefaultTokenContext.ARGUMENT_ID:
177: completionType = CompletionType.ATTRIBUTE;
178: typedChars = token.getImage().substring(0,
179: caretOffset - token.getOffset());
180: ;
181: break;
182:
183: //some random character
184: case XMLDefaultTokenContext.CHARACTER_ID:
185: //user enters = character, we should ignore all other operators
186: case XMLDefaultTokenContext.OPERATOR_ID:
187: completionType = CompletionType.NONE;
188: break;
189: //user enters either ' or "
190: case XMLDefaultTokenContext.VALUE_ID:
191: if (!tokenBoundary) {
192: completionType = CompletionType.ATTRIBUTE_VALUE;
193: typedChars = token.getImage().substring(1,
194: caretOffset - token.getOffset());
195: } else {
196: completionType = CompletionType.NONE;
197: }
198: break;
199:
200: //user enters white-space character
201: case XMLDefaultTokenContext.WS_ID:
202: completionType = CompletionType.NONE;
203: TokenItem prev = token.getPrevious();
204: while (prev != null
205: && (prev.getTokenID().getNumericID() == XMLDefaultTokenContext.WS_ID)) {
206: prev = prev.getPrevious();
207: }
208:
209: if (prev.getTokenID().getNumericID() == XMLDefaultTokenContext.ARGUMENT_ID) {
210: typedChars = prev.getImage();
211: completionType = CompletionType.ATTRIBUTE;
212: } else if ((prev.getTokenID().getNumericID() == XMLDefaultTokenContext.VALUE_ID)
213: || (prev.getTokenID().getNumericID() == XMLDefaultTokenContext.TAG_ID)) {
214: completionType = CompletionType.ATTRIBUTE;
215: }
216: break;
217:
218: default:
219: completionType = CompletionType.NONE;
220: break;
221: }
222: }
223:
224: public CompletionType getCompletionType() {
225: return completionType;
226: }
227:
228: public String getTypedPrefix() {
229: return typedChars;
230: }
231:
232: public Document getDocument() {
233: return this .doc;
234: }
235:
236: public DocumentContext getDocumentContext() {
237: return this .documentContext;
238: }
239:
240: public int getCaretOffset() {
241: return caretOffset;
242: }
243:
244: public Node getTag() {
245: SyntaxElement element = documentContext.getCurrentElement();
246: return (element instanceof Tag) ? (Node) element : null;
247: }
248:
249: public TokenItem getCurrentToken() {
250: return documentContext.getCurrentToken();
251: }
252:
253: public List<String> getExistingAttributes() {
254: if (existingAttributes != null)
255: return existingAttributes;
256: existingAttributes = new ArrayList<String>();
257: TokenItem item = documentContext.getCurrentToken()
258: .getPrevious();
259: while (item != null) {
260: if (item.getTokenID().getNumericID() == XMLDefaultTokenContext.TAG_ID)
261: break;
262: if (item.getTokenID().getNumericID() == XMLDefaultTokenContext.ARGUMENT_ID) {
263: existingAttributes.add(item.getImage());
264: }
265: item = item.getPrevious();
266: }
267: return existingAttributes;
268: }
269: }
|