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.editor;
043:
044: import java.util.ArrayList;
045: import java.util.Collection;
046: import java.util.Collections;
047: import java.util.HashMap;
048: import java.util.List;
049: import java.util.Map.Entry;
050: import java.util.Stack;
051: import javax.swing.text.BadLocationException;
052: import javax.swing.text.Document;
053: import org.netbeans.editor.BaseDocument;
054: import org.netbeans.editor.TokenItem;
055: import org.netbeans.modules.xml.text.syntax.SyntaxElement;
056: import org.netbeans.modules.xml.text.syntax.XMLSyntaxSupport;
057: import org.netbeans.modules.xml.text.syntax.dom.EmptyTag;
058: import org.netbeans.modules.xml.text.syntax.dom.EndTag;
059: import org.netbeans.modules.xml.text.syntax.dom.StartTag;
060: import org.netbeans.modules.xml.text.syntax.dom.Tag;
061: import org.w3c.dom.Attr;
062: import org.w3c.dom.NamedNodeMap;
063: import org.w3c.dom.Node;
064:
065: /**
066: * Tracks context information for XML files
067: *
068: * @author Rohan Ranade
069: */
070: public class DocumentContext {
071:
072: public static final String PREFIX = "ns"; //NOI18N
073: public static final String XSI_SCHEMALOCATION = "schemaLocation"; //NOI18N
074: public static final String XSI_NONS_SCHEMALOCATION = "noNamespaceSchemaLocation"; //NOI18N
075:
076: private Document document;
077: private XMLSyntaxSupport syntaxSupport;
078: private int caretOffset = -1;
079: private SyntaxElement element;
080: private TokenItem token;
081: private boolean valid = false;
082: private StartTag docRoot;
083: private String defaultNamespace;
084: private HashMap<String, String> declaredNamespaces = new HashMap<String, String>();
085: private String schemaLocation;
086: private String noNamespaceSchemaLocation;
087:
088: DocumentContext(Document document) {
089: this .document = document;
090: this .syntaxSupport = (XMLSyntaxSupport) ((BaseDocument) document)
091: .getSyntaxSupport();
092: }
093:
094: public void reset(int caretOffset) {
095: this .caretOffset = caretOffset;
096: initialize();
097: }
098:
099: private void initialize() {
100: valid = true;
101: declaredNamespaces.clear();
102: try {
103: element = syntaxSupport.getElementChain(caretOffset);
104: token = syntaxSupport.getTokenChain(caretOffset, Math.min(
105: document.getLength(), caretOffset + 1));
106: this .docRoot = ContextUtilities.getRoot(element);
107: populateNamespaces();
108: } catch (BadLocationException ex) {
109: // No context support available in this case
110: valid = false;
111: }
112: }
113:
114: public boolean isValid() {
115: return this .valid;
116: }
117:
118: public int getCurrentTokenId() {
119: if (isValid()) {
120: return token.getTokenID().getNumericID();
121: } else {
122: return -1;
123: }
124: }
125:
126: public TokenItem getCurrentToken() {
127: if (isValid()) {
128: return token;
129: } else {
130: return null;
131: }
132: }
133:
134: public String getCurrentTokenImage() {
135: if (isValid()) {
136: return token.getImage();
137: } else {
138: return null;
139: }
140: }
141:
142: public SyntaxElement getCurrentElement() {
143: return this .element;
144: }
145:
146: public List<String> getPathFromRoot() {
147: if (isValid()) {
148: SyntaxElement elementRef = this .element;
149: Stack<SyntaxElement> stack = new Stack<SyntaxElement>();
150:
151: while (elementRef != null) {
152: if ((elementRef instanceof EndTag)
153: || (elementRef instanceof EmptyTag && stack
154: .isEmpty())
155: || (elementRef instanceof StartTag && stack
156: .isEmpty())) {
157: stack.push(elementRef);
158: elementRef = elementRef.getPrevious();
159: continue;
160: }
161: if (elementRef instanceof StartTag) {
162: StartTag start = (StartTag) elementRef;
163: if (stack.peek() instanceof EndTag) {
164: EndTag end = (EndTag) stack.peek();
165: if (end.getTagName().equals(start.getTagName())) {
166: stack.pop();
167: }
168: } else {
169: SyntaxElement e = (SyntaxElement) stack.peek();
170: String tagAtTop = (e instanceof StartTag) ? ((StartTag) e)
171: .getTagName()
172: : ((EmptyTag) e).getTagName();
173: stack.push(elementRef);
174: }
175: }
176: elementRef = elementRef.getPrevious();
177: }
178:
179: return createPath(stack);
180: }
181:
182: return Collections.emptyList();
183: }
184:
185: private List<String> createPath(Stack<SyntaxElement> stack) {
186: ArrayList<String> pathList = new ArrayList<String>();
187: while (!stack.isEmpty()) {
188: SyntaxElement top = stack.pop();
189: String tagName = (top instanceof StartTag) ? ((StartTag) top)
190: .getTagName()
191: : ((EmptyTag) top).getTagName();
192: if (tagName != null) {
193: pathList.add(tagName);
194: }
195: }
196:
197: return Collections.unmodifiableList(pathList);
198: }
199:
200: public Document getDocument() {
201: return this .document;
202: }
203:
204: public String lookupNamespacePrefix(String prefix) {
205: return declaredNamespaces.get(prefix);
206: }
207:
208: public String getNamespacePrefix(String namespace) {
209: for (Entry<String, String> entry : declaredNamespaces
210: .entrySet()) {
211: if (entry.getValue().equals(namespace)) {
212: return entry.getKey();
213: }
214: }
215:
216: return null;
217: }
218:
219: public Collection<String> getDeclaredNamespaces() {
220: return declaredNamespaces.values();
221: }
222:
223: public StartTag getDocRoot() {
224: return docRoot;
225: }
226:
227: public int getCaretOffset() {
228: return this .caretOffset;
229: }
230:
231: private void populateNamespaces() {
232: // Find the a start or empty tag just before the current syntax element.
233: SyntaxElement element = this .element;
234: while (element != null && !(element instanceof StartTag)
235: && !(element instanceof EmptyTag)) {
236: element = element.getPrevious();
237: }
238: if (element == null) {
239: return;
240: }
241:
242: // To find all namespace declarations active at the caret offset, we
243: // need to look at xmlns attributes of the current element and its ancestors.
244: Node node = (Node) element;
245: while (node != null) {
246: if (node instanceof StartTag || node instanceof EmptyTag) {
247: NamedNodeMap attributes = ((Tag) node).getAttributes();
248: for (int index = 0; index < attributes.getLength(); index++) {
249: Attr attr = (Attr) attributes.item(index);
250: String attrName = attr.getName();
251: String attrValue = attr.getValue();
252: if (attrName == null || attrValue == null) {
253: continue;
254: }
255: String prefix = ContextUtilities
256: .getPrefixFromNamespaceDeclaration(attrName);
257: if (prefix == null) {
258: continue;
259: }
260: // Avoid overwriting a namespace declaration "closer" to the caret offset.
261: if (!declaredNamespaces.containsKey(prefix)) {
262: declaredNamespaces.put(prefix, attrValue);
263: }
264: }
265: }
266: node = node.getParentNode();
267: }
268: }
269:
270: public String getNoNamespaceSchemaLocation() {
271: return noNamespaceSchemaLocation;
272: }
273:
274: public String getSchemaLocation() {
275: return schemaLocation;
276: }
277:
278: @Override
279: public boolean equals(Object obj) {
280: if (obj == null) {
281: return false;
282: }
283: if (getClass() != obj.getClass()) {
284: return false;
285: }
286: final DocumentContext other = (DocumentContext) obj;
287: if (this .document != other.document
288: && (this .document == null || !this .document
289: .equals(other.document))) {
290: return false;
291: }
292: return true;
293: }
294:
295: @Override
296: public int hashCode() {
297: int hash = 5;
298: hash = 61
299: * hash
300: + (this .document != null ? this .document.hashCode() : 0);
301: return hash;
302: }
303: }
|