| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.jdt; |
| |
| import com.google.gwt.dev.javac.JsniCollector; |
| import com.google.gwt.dev.jjs.InternalCompilerException; |
| import com.google.gwt.dev.jjs.SourceOrigin; |
| import com.google.gwt.dev.js.JsParser; |
| import com.google.gwt.dev.js.JsParserException; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsNameRef; |
| import com.google.gwt.dev.js.ast.JsProgram; |
| import com.google.gwt.dev.js.ast.JsStatement; |
| import com.google.gwt.dev.js.ast.JsVisitor; |
| |
| import org.eclipse.jdt.internal.compiler.ast.Argument; |
| import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.lookup.ClassScope; |
| |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.util.Collections; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Walks the AST to find references to Java identifiers from within JSNI blocks. |
| * By default, it does a full JavaScript parse to accurately find JSNI |
| * references. If {@link #beSloppy()} is called, then it will run much more |
| * quickly but it will return a superset of the actual JSNI references. |
| */ |
| public class FindJsniRefVisitor extends SafeASTVisitor { |
| private final Set<String> jsniRefs = new LinkedHashSet<String>(); |
| |
| public Set<String> getJsniRefs() { |
| return Collections.unmodifiableSet(jsniRefs); |
| } |
| |
| @Override |
| public boolean visit(MethodDeclaration methodDeclaration, ClassScope scope) { |
| if (!methodDeclaration.isNative()) { |
| return false; |
| } |
| |
| // Handle JSNI block |
| String jsniCode = getJSNICode(methodDeclaration); |
| if (jsniCode == null) { |
| return false; |
| } |
| if (jsniCode.indexOf('@') < 0) { |
| // short cut: if there are no at signs, there are no JSNI refs |
| return false; |
| } |
| |
| findJsniRefsAccurately(methodDeclaration, jsniCode); |
| return false; |
| } |
| |
| private void findJsniRefsAccurately(MethodDeclaration methodDeclaration, String jsniCode) |
| throws InternalCompilerException { |
| JsProgram jsProgram = new JsProgram(); |
| |
| String syntheticFnHeader = "function("; |
| boolean first = true; |
| if (methodDeclaration.arguments != null) { |
| for (int i = 0, c = methodDeclaration.arguments.length; i < c; ++i) { |
| Argument arg = methodDeclaration.arguments[i]; |
| if (first) { |
| first = false; |
| } else { |
| syntheticFnHeader += ','; |
| } |
| syntheticFnHeader += String.valueOf(arg.name); |
| } |
| } |
| syntheticFnHeader += ')'; |
| StringReader sr = new StringReader(syntheticFnHeader + '\n' + jsniCode); |
| try { |
| // start at -1 to avoid counting our synthetic header |
| List<JsStatement> result = JsParser.parse(SourceOrigin.UNKNOWN, jsProgram.getScope(), sr); |
| new JsVisitor() { |
| @Override |
| public void endVisit(JsNameRef x, JsContext ctx) { |
| String ident = x.getIdent(); |
| if (ident.charAt(0) == '@') { |
| jsniRefs.add(ident.substring(1)); |
| } |
| } |
| }.acceptList(result); |
| } catch (IOException e) { |
| throw new InternalCompilerException(e.getMessage(), e); |
| } catch (JsParserException e) { |
| // ignore, we only care about finding valid references |
| } |
| } |
| |
| private String getJSNICode(MethodDeclaration methodDeclaration) { |
| char[] source = methodDeclaration.compilationResult().getCompilationUnit().getContents(); |
| String jsniCode = |
| String.valueOf(source, methodDeclaration.bodyStart, methodDeclaration.bodyEnd |
| - methodDeclaration.bodyStart + 1); |
| int startPos = jsniCode.indexOf(JsniCollector.JSNI_BLOCK_START); |
| int endPos = jsniCode.lastIndexOf(JsniCollector.JSNI_BLOCK_END); |
| if (startPos < 0 || endPos < 0) { |
| return null; // ignore the error |
| } |
| |
| // move up to open brace |
| startPos += JsniCollector.JSNI_BLOCK_START.length() - 1; |
| // move past close brace |
| endPos += 1; |
| |
| jsniCode = jsniCode.substring(startPos, endPos); |
| return jsniCode; |
| } |
| |
| } |