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.languages.javascript;
043:
044: import java.util.Arrays;
045: import java.util.HashMap;
046: import java.util.HashSet;
047: import java.util.ListIterator;
048: import java.util.ListIterator;
049: import java.util.Map;
050: import java.util.Set;
051: import java.io.IOException;
052: import java.io.Reader;
053: import java.io.Writer;
054: import java.lang.reflect.InvocationTargetException;
055: import java.lang.reflect.Method;
056: import java.util.ArrayList;
057: import java.util.Iterator;
058: import java.util.List;
059: import javax.swing.text.AbstractDocument;
060: import javax.swing.text.BadLocationException;
061: import javax.swing.text.Document;
062: import javax.swing.text.JTextComponent;
063:
064: import org.netbeans.api.languages.ASTItem;
065: import org.netbeans.api.languages.CharInput;
066: import org.netbeans.api.languages.LibrarySupport;
067: import org.netbeans.api.languages.ASTNode;
068: import org.netbeans.api.languages.ASTPath;
069: import org.netbeans.api.languages.SyntaxContext;
070: import org.netbeans.api.lexer.Token;
071: import org.netbeans.api.lexer.TokenHierarchy;
072: import org.netbeans.api.lexer.TokenSequence;
073: import org.netbeans.api.languages.Context;
074: import org.netbeans.api.languages.LibrarySupport;
075: import org.netbeans.api.languages.SyntaxContext;
076: import org.netbeans.api.languages.ASTNode;
077: import org.netbeans.api.languages.ASTToken;
078: import org.netbeans.modules.editor.NbEditorDocument;
079: import org.netbeans.modules.editor.NbEditorUtilities;
080: import org.openide.DialogDisplayer;
081: import org.openide.ErrorManager;
082: import org.openide.ErrorManager;
083: import org.openide.NotifyDescriptor;
084: import org.openide.cookies.SaveCookie;
085: import org.openide.loaders.DataObject;
086: import org.openide.windows.IOProvider;
087: import org.openide.windows.InputOutput;
088: import org.netbeans.api.languages.CompletionItem;
089: import org.netbeans.api.languages.Language;
090: import org.netbeans.api.languages.LanguageDefinitionNotFoundException;
091: import org.netbeans.api.languages.LanguagesManager;
092: import org.openide.filesystems.FileObject;
093: import org.openide.util.RequestProcessor;
094:
095: /**
096: *
097: * @author Jan Jancura, Dan Prusa
098: */
099: public class JavaScript {
100:
101: private static final String DOC = "org/netbeans/modules/languages/javascript/Documentation.xml";
102: private static final String DOM0 = "org/netbeans/modules/languages/javascript/DOM0.xml";
103: private static final String DOM1 = "org/netbeans/modules/languages/javascript/DOM1.xml";
104: private static final String DOM2 = "org/netbeans/modules/languages/javascript/DOM2.xml";
105: private static final String MIME_TYPE = "text/javascript";
106:
107: private static Set<Integer> regExp = new HashSet<Integer>();
108: static {
109: regExp.add(new Integer(','));
110: regExp.add(new Integer(')'));
111: regExp.add(new Integer(';'));
112: regExp.add(new Integer(']'));
113: }
114:
115: public static Object[] parseRegularExpression(CharInput input) {
116: if (input.read() != '/')
117: throw new InternalError();
118: int start = input.getIndex();
119: try {
120: Language language = LanguagesManager.get().getLanguage(
121: MIME_TYPE);
122: while (!input.eof() && input.next() != '/') {
123: if (input.next() == '\r' || input.next() == '\n') {
124: input.setIndex(start);
125: return new Object[] {
126: ASTToken.create(language, "js_operator",
127: "", 0, 0, null), null };
128: }
129: if (input.next() == '\\')
130: input.read();
131: input.read();
132: }
133: while (input.next() == '/')
134: input.read();
135: while (!input.eof()) {
136: int ch = input.next();
137: if (ch != 'g' && ch != 'i' && ch != 'm')
138: break;
139: input.read();
140: }
141: int end = input.getIndex();
142: char car = input.eof() ? 0 : input.next();
143: boolean newLineDetected = false;
144: while (!input.eof()
145: && (car == ' ' || car == '\t' || car == '\n' || car == '\r')) {
146: newLineDetected = newLineDetected || car == '\n';
147: input.read();
148: if (!input.eof()) {
149: car = input.next();
150: }
151: }
152: if (!input.eof() && input.next() == '.') {
153: input.read();
154: if (input.next() >= '0' && input.next() <= '9') {
155: input.setIndex(start);
156: return new Object[] {
157: ASTToken.create(language, "js_operator",
158: "", 0, 0, null), null };
159: } else {
160: input.setIndex(end);
161: return new Object[] {
162: ASTToken.create(language,
163: "js_regularExpression", "", 0, 0,
164: null), null };
165: }
166: }
167: if (newLineDetected || input.eof()
168: || regExp.contains(new Integer(input.next()))) {
169: input.setIndex(end);
170: return new Object[] {
171: ASTToken.create(language,
172: "js_regularExpression", "", 0, 0, null),
173: null };
174: }
175: input.setIndex(start);
176: return new Object[] {
177: ASTToken.create(language, "js_operator", "", 0, 0,
178: null), null };
179: } catch (LanguageDefinitionNotFoundException ex) {
180: ex.printStackTrace();
181: return null;
182: }
183: }
184:
185: public static String variableName(SyntaxContext context) {
186: ASTPath path = context.getASTPath();
187: ListIterator<ASTItem> it = path.listIterator();
188: while (it.hasNext()) {
189: ASTItem item = it.next();
190: if ((item instanceof ASTNode)
191: && ((ASTNode) item).getNT().equals(
192: "FunctionDeclaration")
193: && ((ASTNode) item)
194: .getNode("FunctionHeader.FunctionName") == null) {
195: return null;
196: }
197: } // while
198: return ((ASTNode) path.getLeaf()).getNode("VariableName")
199: .getAsText();
200: }
201:
202: public static String variableIcon(SyntaxContext context) {
203: ASTPath path = context.getASTPath();
204: ASTNode node = (ASTNode) path.getLeaf();
205: node = node.getNode("Initializer.ObjectLiteral");
206: if (node != null) {
207: return "/org/netbeans/modules/languages/resources/class.gif";
208: }
209: return "/org/netbeans/modules/languages/resources/variable.gif";
210: }
211:
212: private static Set<String> objectNTs = new HashSet<String>();
213: static {
214: objectNTs.add("VariableDeclaration");
215: objectNTs.add("Initializer");
216: objectNTs.add("ObjectLiteral");
217: objectNTs.add("PropertyNameAndValue");
218: }
219:
220: public static String propertyName(SyntaxContext context) {
221: ASTPath path = context.getASTPath();
222: boolean in = false;
223: for (int i = 0; i < path.size(); i++) {
224: if (!(path.get(i) instanceof ASTNode))
225: continue;
226: ASTNode node = (ASTNode) path.get(i);
227: if (node.getNT().equals("FunctionDeclaration"))
228: return null;
229: if (in && !objectNTs.contains(node.getNT()))
230: return null;
231: if (node.getNT().equals("VariableStatement"))
232: in = true;
233: }
234: if (!in)
235: return null;
236: ASTNode node = (ASTNode) path.getLeaf();
237: String name = node.getNode("PropertyName").getAsText();
238: node = node.getNode("FunctionDeclaration.FormalParameterList");
239: if (node != null) {
240: name += " (" + getParametersAsText(node) + ")";
241: }
242: return name;
243: }
244:
245: public static String propertyIcon(SyntaxContext context) {
246: ASTPath path = context.getASTPath();
247: ASTNode n = (ASTNode) path.getLeaf();
248: if (n.getNode("FunctionDeclaration") != null)
249: return "/org/netbeans/modules/languages/resources/method.gif";
250: if (n.getNode("ObjectLiteral") != null)
251: return "/org/netbeans/modules/languages/resources/class.gif";
252: return "/org/netbeans/modules/languages/resources/variable.gif";
253: }
254:
255: public static String functionName(SyntaxContext context) {
256: ASTPath path = context.getASTPath();
257: ListIterator<ASTItem> it = path.listIterator();
258: while (it.hasNext()) {
259: ASTItem item = it.next();
260: if (!(item instanceof ASTNode))
261: continue;
262: ASTNode n = (ASTNode) item;
263: if (n.getNT().equals("ObjectLiteral")) {
264: return null;
265: }
266: if (n.getNT().equals("FunctionDeclaration")
267: && n != path.getLeaf()
268: && n.getNode("FunctionHeader.FunctionName") == null) {
269: return null;
270: }
271: } // while
272:
273: ASTNode node = (ASTNode) path.getLeaf();
274: String name = null;
275: ASTNode nameNode = node.getNode("FunctionHeader.FunctionName");
276: if (nameNode == null) {
277: return null;
278: }
279: name = nameNode.getAsText();
280: ASTNode parametersNode = node.getNode("FormalParameterList");
281: return name + " (" + getParametersAsText(parametersNode) + ")";
282: }
283:
284: // code completion .........................................................
285:
286: public static List<CompletionItem> completionItems(Context context) {
287: if (context instanceof SyntaxContext) {
288: List<CompletionItem> result = new ArrayList<CompletionItem>();
289: return merge(result);
290: }
291:
292: AbstractDocument document = (AbstractDocument) context
293: .getDocument();
294: document.readLock();
295: try {
296: TokenSequence tokenSequence = getTokenSequence(context);
297: List<CompletionItem> result = new ArrayList<CompletionItem>();
298: Token token = previousToken(tokenSequence);
299: String tokenText = token.text().toString();
300: String libraryContext = null;
301: if (tokenText.equals("new")) {
302: result.addAll(getLibrary().getCompletionItems(
303: "constructor"));
304: return merge(result);
305: }
306: if (tokenText.equals(".")) {
307: token = previousToken(tokenSequence);
308: if (token.id().name().endsWith("identifier"))
309: libraryContext = token.text().toString();
310: } else if (token.id().name().endsWith("identifier")) {
311: token = previousToken(tokenSequence);
312: if (token.text().toString().equals(".")) {
313: token = previousToken(tokenSequence);
314: if (token.id().name().endsWith("identifier"))
315: libraryContext = token.text().toString();
316: } else if (token.text().toString().equals("new")) {
317: result.addAll(getLibrary().getCompletionItems(
318: "constructor"));
319: return merge(result);
320: }
321: }
322:
323: if (libraryContext != null) {
324: result.addAll(getLibrary().getCompletionItems(
325: libraryContext));
326: result
327: .addAll(getLibrary().getCompletionItems(
328: "member"));
329: } else
330: result.addAll(getLibrary().getCompletionItems("root"));
331: return merge(result);
332: } finally {
333: document.readUnlock();
334: }
335: }
336:
337: private static TokenSequence getTokenSequence(Context context) {
338: TokenHierarchy tokenHierarchy = TokenHierarchy.get(context
339: .getDocument());
340: TokenSequence ts = tokenHierarchy.tokenSequence();
341: while (true) {
342: ts.move(context.getOffset());
343: if (!ts.moveNext())
344: return ts;
345: TokenSequence ts2 = ts.embedded();
346: if (ts2 == null)
347: return ts;
348: ts = ts2;
349: }
350: }
351:
352: private static List<CompletionItem> merge(List<CompletionItem> items) {
353: Map<String, CompletionItem> map = new HashMap<String, CompletionItem>();
354: Iterator<CompletionItem> it = items.iterator();
355: while (it.hasNext()) {
356: CompletionItem completionItem = it.next();
357: CompletionItem current = map.get(completionItem.getText());
358: if (current != null) {
359: String library = current.getLibrary();
360: if (library == null)
361: library = "";
362: if (completionItem.getLibrary() != null
363: && library.indexOf(completionItem.getLibrary()) < 0)
364: library += ',' + completionItem.getLibrary();
365: completionItem = CompletionItem.create(current
366: .getText(), current.getDescription(), library,
367: current.getType(), current.getPriority());
368: }
369: map.put(completionItem.getText(), completionItem);
370: }
371: return new ArrayList<CompletionItem>(map.values());
372: }
373:
374: private static Token previousToken(TokenSequence ts) {
375: do {
376: if (!ts.movePrevious())
377: return ts.token();
378: } while (ts.token().id().name().endsWith("whitespace")
379: || ts.token().id().name().endsWith("comment"));
380: return ts.token();
381: }
382:
383: // actions .................................................................
384:
385: public static void performDeleteCurrentMethod(ASTNode node,
386: JTextComponent comp) {
387: NbEditorDocument doc = (NbEditorDocument) comp.getDocument();
388: int position = comp.getCaretPosition();
389: ASTPath path = node.findPath(position);
390: ASTNode methodNode = null;
391: for (Iterator iter = path.listIterator(); iter.hasNext();) {
392: Object obj = iter.next();
393: if (!(obj instanceof ASTNode))
394: break;
395: ASTNode n = (ASTNode) obj;
396: if ("FunctionDeclaration".equals(n.getNT())) { // NOI18N
397: methodNode = n;
398: } // if
399: } // for
400: if (methodNode != null) {
401: try {
402: doc.remove(methodNode.getOffset(), methodNode
403: .getLength());
404: } catch (BadLocationException e) {
405: ErrorManager.getDefault().notify(e);
406: }
407: }
408: }
409:
410: public static boolean enabledDeleteCurrentMethod(ASTNode node,
411: JTextComponent comp) {
412: int position = comp.getCaretPosition();
413: ASTPath path = node.findPath(position);
414: if (path == null)
415: return false;
416: for (Iterator iter = path.listIterator(); iter.hasNext();) {
417: Object obj = iter.next();
418: if (!(obj instanceof ASTNode))
419: return false;
420: ASTNode n = (ASTNode) obj;
421: if ("FunctionDeclaration".equals(n.getNT())) { // NOI18N
422: return true;
423: } // if
424: } // for
425: return false;
426: }
427:
428: public static void performRun(final ASTNode node,
429: final JTextComponent comp) {
430: RequestProcessor.getDefault().post(new Runnable() {
431: public void run() {
432: ClassLoader cl = JavaScript.class.getClassLoader();
433: InputOutput io = null;
434: FileObject fo = null;
435: try {
436: // ScriptEngineManager manager = new ScriptEngineManager ();
437: // ScriptEngine engine = manager.getEngineByMimeType ("text/javascript");
438: Class managerClass = cl
439: .loadClass("javax.script.ScriptEngineManager");
440: Object manager = managerClass.newInstance();
441: Method getEngineByMimeType = managerClass
442: .getMethod("getEngineByMimeType",
443: new Class[] { String.class });
444: Object engine = getEngineByMimeType.invoke(manager,
445: new Object[] { "text/javascript" });
446:
447: Document doc = comp.getDocument();
448: DataObject dob = NbEditorUtilities
449: .getDataObject(doc);
450: String name = dob.getPrimaryFile().getNameExt();
451: fo = dob.getPrimaryFile();
452: SaveCookie saveCookie = dob.getLookup().lookup(
453: SaveCookie.class);
454: if (saveCookie != null)
455: try {
456: saveCookie.save();
457: } catch (IOException ex) {
458: ErrorManager.getDefault().notify(ex);
459: }
460:
461: // ScriptContext context = engine.getContext ();
462: Class engineClass = cl
463: .loadClass("javax.script.ScriptEngine");
464: Method getContext = engineClass.getMethod(
465: "getContext", new Class[] {});
466: Object context = getContext.invoke(engine,
467: new Object[] {});
468: Method put = engineClass.getMethod("put",
469: new Class[] { String.class, Object.class });
470: put.invoke(engine, new Object[] {
471: "javax.script.filename", fo.getPath() });
472:
473: io = IOProvider.getDefault().getIO("Run " + name,
474: false);
475:
476: // context.setWriter (io.getOut ());
477: // context.setErrorWriter (io.getErr ());
478: // context.setReader (io.getIn ());
479: Class contextClass = cl
480: .loadClass("javax.script.ScriptContext");
481: Method setWriter = contextClass.getMethod(
482: "setWriter", new Class[] { Writer.class });
483: Method setErrorWriter = contextClass.getMethod(
484: "setErrorWriter",
485: new Class[] { Writer.class });
486: Method setReader = contextClass.getMethod(
487: "setReader", new Class[] { Reader.class });
488: setWriter.invoke(context, new Object[] { io
489: .getOut() });
490: setErrorWriter.invoke(context, new Object[] { io
491: .getErr() });
492: setReader.invoke(context,
493: new Object[] { io.getIn() });
494:
495: io.getOut().reset();
496: io.select();
497:
498: // Object o = engine.eval (doc.getText (0, doc.getLength ()));
499: Method eval = engineClass.getMethod("eval",
500: new Class[] { String.class });
501: Object o = eval.invoke(engine, new Object[] { doc
502: .getText(0, doc.getLength()) });
503:
504: if (o != null)
505: DialogDisplayer.getDefault().notify(
506: new NotifyDescriptor.Message("Result: "
507: + o));
508:
509: } catch (InvocationTargetException ex) {
510: try {
511: Class scriptExceptionClass = cl
512: .loadClass("javax.script.ScriptException");
513: if (ex.getCause() != null
514: && scriptExceptionClass
515: .isAssignableFrom(ex.getCause()
516: .getClass()))
517: if (io != null) {
518: String msg = ex.getCause().getMessage();
519: int line = 0;
520: if (msg.startsWith("sun.org.mozilla")) { //NOI18N
521: msg = msg.substring(msg
522: .indexOf(':') + 1);
523: msg = msg.substring(0,
524: msg.lastIndexOf('('))
525: .trim()
526: + " "
527: + msg
528: .substring(
529: msg
530: .lastIndexOf(')') + 1)
531: .trim();
532: try {
533: line = Integer
534: .valueOf(msg
535: .substring(msg
536: .lastIndexOf("number") + 7)); //NOI18N
537: } catch (NumberFormatException nfe) {
538: //cannot parse, jump at line zero
539: }
540: }
541: io.getOut().println(msg,
542: new OutputProcessor(fo, line));
543: } else
544: ErrorManager.getDefault().notify(ex);
545: } catch (Exception ex2) {
546: ErrorManager.getDefault().notify(ex2);
547: }
548: } catch (Exception ex) {
549: ErrorManager.getDefault().notify(ex);
550: }
551: }
552: });
553: }
554:
555: public static boolean enabledRun(ASTNode node, JTextComponent comp) {
556: try {
557: ClassLoader cl = JavaScript.class.getClassLoader();
558: Class managerClass = cl
559: .loadClass("javax.script.ScriptEngineManager");
560:
561: return managerClass != null;
562: } catch (ClassNotFoundException ex) {
563: return false;
564: }
565: }
566:
567: // helper methods ..........................................................
568:
569: private static LibrarySupport library;
570:
571: private static LibrarySupport getLibrary() {
572: if (library == null)
573: library = LibrarySupport.create(Arrays.asList(new String[] {
574: DOC, DOM0, DOM1, DOM2 }));
575: return library;
576: }
577:
578: private static String getParametersAsText(ASTNode params) {
579: if (params == null)
580: return "";
581: StringBuffer buf = new StringBuffer();
582: for (ASTItem item : params.getChildren()) {
583: if (item instanceof ASTNode) {
584: String nt = ((ASTNode) item).getNT();
585: if ("Parameter".equals(nt)) {
586: Iterator<ASTItem> iter = ((ASTNode) item)
587: .getChildren().iterator();
588: if (iter.hasNext()) {
589: item = iter.next();
590: }
591: }
592: }
593: if (!(item instanceof ASTToken)) {
594: continue;
595: }
596: ASTToken token = (ASTToken) item;
597: String type = token.getTypeName();
598: if ("js_whitespace".equals(type)
599: || "js_comment".equals(type)) {
600: continue;
601: }
602: String id = token.getIdentifier();
603: buf.append(id);
604: if (",".equals(id)) {
605: buf.append(' ');
606: }
607: }
608: return buf.toString();
609: }
610: }
|