| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.codehaus.groovy.tools.groovydoc; |
| |
| import antlr.collections.AST; |
| import org.codehaus.groovy.antlr.GroovySourceAST; |
| import org.codehaus.groovy.antlr.LineColumn; |
| import org.codehaus.groovy.antlr.SourceBuffer; |
| import org.codehaus.groovy.antlr.parser.GroovyTokenTypes; |
| import org.codehaus.groovy.antlr.treewalker.VisitorAdapter; |
| import org.codehaus.groovy.control.ResolveVisitor; |
| import org.codehaus.groovy.groovydoc.GroovyAnnotationRef; |
| import org.codehaus.groovy.groovydoc.GroovyClassDoc; |
| import org.codehaus.groovy.groovydoc.GroovyConstructorDoc; |
| import org.codehaus.groovy.groovydoc.GroovyFieldDoc; |
| import org.codehaus.groovy.groovydoc.GroovyMethodDoc; |
| import org.codehaus.groovy.groovydoc.GroovyType; |
| import org.codehaus.groovy.runtime.DefaultGroovyMethods; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Stack; |
| import java.util.StringTokenizer; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| public class SimpleGroovyClassDocAssembler extends VisitorAdapter implements GroovyTokenTypes { |
| private static final String FS = "/"; |
| private static final Pattern PREV_JAVADOC_COMMENT_PATTERN = Pattern.compile("(?s)/\\*\\*(.*?)\\*/"); |
| private final Stack<GroovySourceAST> stack; |
| private final Map<String, GroovyClassDoc> classDocs; |
| private final List<String> importedClassesAndPackages; |
| private final Map<String, String> aliases; |
| private final List<LinkArgument> links; |
| private final Properties properties; |
| private SimpleGroovyFieldDoc currentFieldDoc; |
| private final SourceBuffer sourceBuffer; |
| private String packagePath; |
| private LineColumn lastLineCol; |
| private boolean insideEnum; |
| private Map<String, SimpleGroovyClassDoc> foundClasses; |
| private final boolean isGroovy; |
| private final boolean deferSetup; |
| private final String className; |
| |
| public SimpleGroovyClassDocAssembler(String packagePath, String file, SourceBuffer sourceBuffer, List<LinkArgument> links, Properties properties, boolean isGroovy) { |
| this.sourceBuffer = sourceBuffer; |
| this.packagePath = packagePath; |
| this.links = links; |
| this.properties = properties; |
| this.isGroovy = isGroovy; |
| |
| stack = new Stack<GroovySourceAST>(); |
| classDocs = new LinkedHashMap<String, GroovyClassDoc>(); |
| if (file != null && file.contains(".")) { |
| // todo: replace this simple idea of default class name |
| int idx = file.lastIndexOf("."); |
| className = file.substring(0, idx); |
| } else { |
| className = file; |
| } |
| |
| deferSetup = packagePath.equals("DefaultPackage"); |
| importedClassesAndPackages = new ArrayList<String>(); |
| aliases = new LinkedHashMap<String, String>(); |
| if (!deferSetup) setUpImports(packagePath, links, isGroovy, className); |
| lastLineCol = new LineColumn(1, 1); |
| } |
| |
| private void setUpImports(String packagePath, List<LinkArgument> links, boolean isGroovy, String className) { |
| importedClassesAndPackages.add(packagePath + "/*"); // everything in this package |
| if (isGroovy) { |
| for (String pkg : ResolveVisitor.DEFAULT_IMPORTS) { |
| importedClassesAndPackages.add(pkg.replace('.', '/') + "*"); |
| } |
| } else { |
| importedClassesAndPackages.add("java/lang/*"); |
| } |
| SimpleGroovyClassDoc currentClassDoc = new SimpleGroovyClassDoc(importedClassesAndPackages, aliases, className, links); |
| currentClassDoc.setFullPathName(packagePath + FS + className); |
| currentClassDoc.setGroovy(isGroovy); |
| classDocs.put(currentClassDoc.getFullPathName(), currentClassDoc); |
| } |
| |
| public Map<String, GroovyClassDoc> getGroovyClassDocs() { |
| postProcessClassDocs(); |
| return classDocs; |
| } |
| |
| @Override |
| public void visitInterfaceDef(GroovySourceAST t, int visit) { |
| visitClassDef(t, visit); |
| } |
| |
| @Override |
| public void visitTraitDef(GroovySourceAST t, int visit) { |
| visitClassDef(t, visit); |
| } |
| |
| @Override |
| public void visitEnumDef(GroovySourceAST t, int visit) { |
| visitClassDef(t, visit); |
| SimpleGroovyClassDoc currentClassDoc = getCurrentOrTopLevelClassDoc(t); |
| if (visit == CLOSING_VISIT && currentClassDoc != null) { |
| adjustForAutomaticEnumMethods(currentClassDoc); |
| } |
| } |
| |
| @Override |
| public void visitAnnotationDef(GroovySourceAST t, int visit) { |
| visitClassDef(t, visit); |
| } |
| |
| @Override |
| public void visitClassDef(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT) { |
| SimpleGroovyClassDoc parent = getCurrentClassDoc(); |
| String shortName = getIdentFor(t); |
| String className = shortName; |
| if (parent != null && isNested() && !insideAnonymousInnerClass()) { |
| className = parent.name() + "." + className; |
| } else { |
| foundClasses = new LinkedHashMap<String, SimpleGroovyClassDoc>(); |
| } |
| SimpleGroovyClassDoc current = (SimpleGroovyClassDoc) classDocs.get(packagePath + FS + className); |
| if (current == null) { |
| current = new SimpleGroovyClassDoc(importedClassesAndPackages, aliases, className, links); |
| current.setGroovy(isGroovy); |
| } |
| current.setRawCommentText(getJavaDocCommentsBeforeNode(t)); |
| current.setFullPathName(packagePath + FS + current.name()); |
| current.setTokenType(t.getType()); |
| current.setNameWithTypeArgs(getIdentPlusTypeArgsFor(t)); |
| processAnnotations(t, current); |
| processModifiers(t, current); |
| classDocs.put(current.getFullPathName(), current); |
| foundClasses.put(shortName, current); |
| if (parent != null) { |
| parent.addNested(current); |
| current.setOuter(parent); |
| } |
| } |
| } |
| |
| @Override |
| public void visitPackageDef(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT && deferSetup) { |
| String packageWithSlashes = extractImportPath(t); |
| setUpImports(packageWithSlashes, links, isGroovy, className); |
| } |
| } |
| |
| @Override |
| public void visitImport(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT) { |
| String importTextWithSlashesInsteadOfDots = extractImportPath(t); |
| |
| GroovySourceAST child = t.childOfType(LITERAL_as); |
| if (child != null) { |
| String alias = child.childOfType(DOT).getNextSibling().getText(); |
| |
| child = child.childOfType(DOT); |
| importTextWithSlashesInsteadOfDots = recurseDownImportBranch(child); |
| |
| aliases.put(alias, importTextWithSlashesInsteadOfDots); |
| } |
| |
| importedClassesAndPackages.add(importTextWithSlashesInsteadOfDots); |
| } |
| } |
| |
| // TODO is this needed so we can click through on default values? |
| // @Override |
| // public void visitStaticImport(GroovySourceAST t, int visit) { |
| // if (visit == OPENING_VISIT) { |
| // // TODO |
| // String importTextWithSlashesInsteadOfDots = extractImportPath(t); |
| // System.out.println(currentClassDoc.name() + " has static import: " + importTextWithSlashesInsteadOfDots); |
| // } |
| // } |
| |
| @Override |
| public void visitExtendsClause(GroovySourceAST t, int visit) { |
| SimpleGroovyClassDoc currentClassDoc = getCurrentClassDoc(); |
| if (visit == OPENING_VISIT) { |
| for (GroovySourceAST superClassNode : findTypeNames(t)) { |
| String superClassName = extractName(superClassNode); |
| if (currentClassDoc.isInterface()) { |
| currentClassDoc.addInterfaceName(superClassName); |
| } else { |
| currentClassDoc.setSuperClassName(superClassName); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visitImplementsClause(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT) { |
| for (GroovySourceAST classNode : findTypeNames(t)) { |
| getCurrentClassDoc().addInterfaceName(extractName(classNode)); |
| } |
| } |
| } |
| |
| private static List<GroovySourceAST> findTypeNames(GroovySourceAST t) { |
| List<GroovySourceAST> types = new ArrayList<GroovySourceAST>(); |
| for (AST child = t.getFirstChild(); child != null; child = child.getNextSibling()) { |
| GroovySourceAST groovySourceAST = (GroovySourceAST) child; |
| if (groovySourceAST.getType() == TYPE) { |
| types.add((GroovySourceAST) groovySourceAST.getFirstChild()); |
| } else { |
| types.add(groovySourceAST); |
| } |
| } |
| return types; |
| } |
| |
| @Override |
| public void visitCtorIdent(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT && !insideEnum && !insideAnonymousInnerClass()) { |
| SimpleGroovyClassDoc currentClassDoc = getCurrentClassDoc(); |
| SimpleGroovyConstructorDoc currentConstructorDoc = new SimpleGroovyConstructorDoc(currentClassDoc.name(), currentClassDoc); |
| currentConstructorDoc.setRawCommentText(getJavaDocCommentsBeforeNode(t)); |
| processModifiers(t, currentConstructorDoc); |
| addParametersTo(t, currentConstructorDoc); |
| processAnnotations(t, currentConstructorDoc); |
| currentClassDoc.add(currentConstructorDoc); |
| } |
| } |
| |
| @Override |
| public void visitMethodDef(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT && !insideEnum && !insideAnonymousInnerClass()) { |
| SimpleGroovyClassDoc currentClassDoc = getCurrentClassDoc(); |
| if (currentClassDoc == null) { |
| // assume we have a script |
| if ("true".equals(properties.getProperty("processScripts", "true"))) { |
| currentClassDoc = getOrMakeScriptClassDoc(); |
| } else { |
| return; |
| } |
| } |
| SimpleGroovyMethodDoc currentMethodDoc = createMethod(t, currentClassDoc); |
| StringBuilder params = new StringBuilder(); |
| getTypeParameters(t.childOfType(TYPE_PARAMETERS), params, "def"); |
| currentMethodDoc.setTypeParameters(params.toString()); |
| currentClassDoc.add(currentMethodDoc); |
| } |
| } |
| |
| private SimpleGroovyClassDoc getOrMakeScriptClassDoc() { |
| SimpleGroovyClassDoc currentClassDoc; |
| if (foundClasses != null && foundClasses.containsKey(className)) { |
| currentClassDoc = foundClasses.get(className); |
| } else { |
| currentClassDoc = new SimpleGroovyClassDoc(importedClassesAndPackages, aliases, className, links); |
| currentClassDoc.setFullPathName(packagePath + FS + className); |
| currentClassDoc.setPublic(true); |
| currentClassDoc.setScript(true); |
| currentClassDoc.setGroovy(isGroovy); |
| currentClassDoc.setSuperClassName("groovy/lang/Script"); |
| if ("true".equals(properties.getProperty("includeMainForScripts", "true"))) { |
| currentClassDoc.add(createMainMethod(currentClassDoc)); |
| } |
| classDocs.put(currentClassDoc.getFullPathName(), currentClassDoc); |
| if (foundClasses == null) { |
| foundClasses = new LinkedHashMap<String, SimpleGroovyClassDoc>(); |
| } |
| foundClasses.put(className, currentClassDoc); |
| } |
| return currentClassDoc; |
| } |
| |
| private void processPropertiesFromGetterSetter(SimpleGroovyMethodDoc currentMethodDoc) { |
| String methodName = currentMethodDoc.name(); |
| int len = methodName.length(); |
| String prefix = null; |
| String propName = null; |
| if (len > 3 && methodName.startsWith("get")) { |
| prefix = "get"; |
| propName = methodName.substring(3); |
| } else if (len > 3 && methodName.startsWith("set")) { |
| prefix = "set"; |
| propName = methodName.substring(3); |
| } else if (len > 2 && methodName.startsWith("is")) { |
| prefix = "is"; |
| propName = methodName.substring(2); |
| } else { |
| // Not a (get/set/is) method that contains a property name |
| return; |
| } |
| |
| SimpleGroovyClassDoc classDoc = getCurrentClassDoc(); |
| // TODO: not sure why but groovy.ui.view.BasicContentPane#buildOutputArea classDoc is null |
| if (classDoc == null) { |
| return; |
| } |
| GroovyMethodDoc[] methods = classDoc.methods(); |
| |
| //find expected method name |
| String expectedMethodName = null; |
| if ("set".equals(prefix) && (currentMethodDoc.parameters().length >= 1 && !currentMethodDoc.parameters()[0].typeName().equals("boolean"))) { |
| expectedMethodName = "get" + propName; |
| } else if ("get".equals(prefix) && !currentMethodDoc.returnType().typeName().equals("boolean")) { |
| expectedMethodName = "set" + propName; |
| } else if ("is".equals(prefix)) { |
| expectedMethodName = "set" + propName; |
| } else { |
| expectedMethodName = "is" + propName; |
| } |
| |
| for (GroovyMethodDoc methodDoc : methods) { |
| if (methodDoc.name().equals(expectedMethodName)) { |
| |
| //extract the field name |
| String fieldName = propName.substring(0, 1).toLowerCase() + propName.substring(1); |
| SimpleGroovyFieldDoc currentFieldDoc = new SimpleGroovyFieldDoc(fieldName, classDoc); |
| |
| //find the type of the field; if it's a setter, need to get the type of the params |
| if(expectedMethodName.startsWith("set") && methodDoc.parameters().length >= 1) { |
| String typeName = methodDoc.parameters()[0].typeName(); |
| currentFieldDoc.setType(new SimpleGroovyType(typeName)); |
| } else { |
| //if it's not setter, get the type info of the return type of the get* method |
| currentFieldDoc.setType(methodDoc.returnType()); |
| } |
| |
| if (methodDoc.isPublic() && currentMethodDoc.isPublic()) { |
| classDoc.addProperty(currentFieldDoc); |
| break; |
| } |
| } |
| } |
| } |
| |
| private SimpleGroovyMethodDoc createMethod(GroovySourceAST t, SimpleGroovyClassDoc currentClassDoc) { |
| String methodName = getIdentFor(t); |
| SimpleGroovyMethodDoc currentMethodDoc = new SimpleGroovyMethodDoc(methodName, currentClassDoc); |
| currentMethodDoc.setRawCommentText(getJavaDocCommentsBeforeNode(t)); |
| processModifiers(t, currentMethodDoc); |
| currentMethodDoc.setReturnType(new SimpleGroovyType(getTypeOrDefault(t))); |
| addParametersTo(t, currentMethodDoc); |
| processAnnotations(t, currentMethodDoc); |
| processPropertiesFromGetterSetter(currentMethodDoc); |
| return currentMethodDoc; |
| } |
| |
| private static GroovyMethodDoc createMainMethod(SimpleGroovyClassDoc currentClassDoc) { |
| SimpleGroovyMethodDoc mainMethod = new SimpleGroovyMethodDoc("main", currentClassDoc); |
| mainMethod.setPublic(true); |
| mainMethod.setStatic(true); |
| mainMethod.setCommentText("Implicit main method for Groovy Scripts"); |
| mainMethod.setFirstSentenceCommentText(mainMethod.commentText()); |
| SimpleGroovyParameter args = new SimpleGroovyParameter("args"); |
| GroovyType argsType = new SimpleGroovyType("java.lang.String[]"); |
| args.setType(argsType); |
| mainMethod.add(args); |
| GroovyType returnType = new SimpleGroovyType("void"); |
| mainMethod.setReturnType(returnType); |
| return mainMethod; |
| } |
| |
| @Override |
| public void visitAnnotationFieldDef(GroovySourceAST t, int visit) { |
| if (isGroovy && visit == OPENING_VISIT) { |
| // TODO shouldn't really be treating annotation fields as methods - remove this hack |
| SimpleGroovyClassDoc currentClassDoc = getCurrentClassDoc(); |
| SimpleGroovyMethodDoc currentMethodDoc = createMethod(t, currentClassDoc); |
| String defaultText = getDefaultValue(t); |
| if (defaultText != null) { |
| String orig = currentMethodDoc.getRawCommentText(); |
| currentMethodDoc.setRawCommentText(orig + "\n* @default " + defaultText); |
| } |
| currentClassDoc.add(currentMethodDoc); |
| } else if (visit == OPENING_VISIT) { |
| // if (visit == OPENING_VISIT) { |
| visitVariableDef(t, visit); |
| String defaultText = getDefaultValue(t); |
| if (isGroovy) { |
| currentFieldDoc.setPublic(true); |
| } |
| if (defaultText != null) { |
| currentFieldDoc.setConstantValueExpression(defaultText); |
| String orig = currentFieldDoc.getRawCommentText(); |
| currentFieldDoc.setRawCommentText(orig + "\n* @default " + defaultText); |
| } |
| } |
| } |
| |
| @Override |
| public void visitEnumConstantDef(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT) { |
| SimpleGroovyClassDoc currentClassDoc = getCurrentClassDoc(); |
| insideEnum = true; |
| String enumConstantName = getIdentFor(t); |
| SimpleGroovyFieldDoc currentEnumConstantDoc = new SimpleGroovyFieldDoc(enumConstantName, currentClassDoc); |
| currentEnumConstantDoc.setRawCommentText(getJavaDocCommentsBeforeNode(t)); |
| processModifiers(t, currentEnumConstantDoc); |
| String typeName = getTypeNodeAsText(t.childOfType(TYPE), currentClassDoc.getTypeDescription()); |
| currentEnumConstantDoc.setType(new SimpleGroovyType(typeName)); |
| currentClassDoc.addEnumConstant(currentEnumConstantDoc); |
| } else if (visit == CLOSING_VISIT) { |
| insideEnum = false; |
| } |
| } |
| |
| @Override |
| public void visitVariableDef(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT && !insideAnonymousInnerClass()) { |
| boolean validField = true; |
| SimpleGroovyClassDoc currentClassDoc = getCurrentClassDoc(); |
| if (currentClassDoc == null) { |
| // assume we have a script (and it may have a @Field) |
| if ("true".equals(properties.getProperty("processScripts", "true"))) { |
| currentClassDoc = getOrMakeScriptClassDoc(); |
| validField = false; |
| } else { |
| return; |
| } |
| } else if (!isFieldDefinition()) { |
| return; |
| } |
| String fieldName = getIdentFor(t); |
| if (fieldName.isEmpty()) return; // multi-assignment |
| currentFieldDoc = new SimpleGroovyFieldDoc(fieldName, currentClassDoc); |
| currentFieldDoc.setRawCommentText(getJavaDocCommentsBeforeNode(t)); |
| boolean isProp = processModifiers(t, currentFieldDoc); |
| currentFieldDoc.setType(new SimpleGroovyType(getTypeOrDefault(t))); |
| processAnnotations(t, currentFieldDoc); |
| if (!validField) { // look for @Field |
| for (GroovyAnnotationRef ref : currentFieldDoc.annotations()) { |
| if ("Field".equals(ref.name()) || "groovy/transform/Field".equals(ref.name())) { |
| validField = true; |
| break; |
| } |
| } |
| } |
| if (!validField) return; |
| if (isProp) { |
| currentClassDoc.addProperty(currentFieldDoc); |
| } else { |
| currentClassDoc.add(currentFieldDoc); |
| } |
| } |
| } |
| |
| @Override |
| public void visitAssign(GroovySourceAST t, int visit) { |
| gobbleComments(t, visit); |
| } |
| |
| @Override |
| public void visitMethodCall(GroovySourceAST t, int visit) { |
| gobbleComments(t, visit); |
| } |
| |
| private void gobbleComments(GroovySourceAST t, int visit) { |
| if (visit == OPENING_VISIT) { |
| SimpleGroovyClassDoc currentClassDoc = getCurrentClassDoc(); |
| if (currentClassDoc == null || currentClassDoc.isScript()) { |
| if (t.getLine() > lastLineCol.getLine() || |
| (t.getLine() == lastLineCol.getLine() && t.getColumn() > lastLineCol.getColumn())) { |
| getJavaDocCommentsBeforeNode(t); |
| // not normally set for non-major types but appropriate for a script |
| lastLineCol = new LineColumn(t.getLine(), t.getColumn()); |
| } |
| } |
| } |
| } |
| |
| // Step through ClassDocs and tie up loose ends |
| private void postProcessClassDocs() { |
| for (GroovyClassDoc groovyClassDoc : classDocs.values()) { |
| SimpleGroovyClassDoc classDoc = (SimpleGroovyClassDoc) groovyClassDoc; |
| |
| // potentially add default constructor to class docs (but not interfaces) |
| if (classDoc.isClass()) { |
| GroovyConstructorDoc[] constructors = classDoc.constructors(); |
| if (constructors != null && constructors.length == 0) { // add default constructor to doc |
| // name of class for the constructor |
| GroovyConstructorDoc constructorDoc = new SimpleGroovyConstructorDoc(classDoc.name(), classDoc); |
| // don't forget to tell the class about this default constructor. |
| classDoc.add(constructorDoc); |
| } |
| } |
| } |
| } |
| |
| private boolean isNested() { |
| return getCurrentClassDoc() != null; |
| } |
| |
| private static boolean isTopLevelConstruct(GroovySourceAST node) { |
| if (node == null) return false; |
| int type = node.getType(); |
| return type == CLASS_DEF || type == INTERFACE_DEF || type == TRAIT_DEF || type == ANNOTATION_DEF || type == ENUM_DEF; |
| } |
| |
| private static void adjustForAutomaticEnumMethods(SimpleGroovyClassDoc currentClassDoc) { |
| SimpleGroovyMethodDoc valueOf = new SimpleGroovyMethodDoc("valueOf", currentClassDoc); |
| valueOf.setRawCommentText("Returns the enum constant of this type with the specified name."); |
| SimpleGroovyParameter parameter = new SimpleGroovyParameter("name"); |
| parameter.setTypeName("String"); |
| valueOf.add(parameter); |
| valueOf.setReturnType(new SimpleGroovyType(currentClassDoc.name())); |
| currentClassDoc.add(valueOf); |
| |
| SimpleGroovyMethodDoc values = new SimpleGroovyMethodDoc("values", currentClassDoc); |
| values.setRawCommentText("Returns an array containing the constants of this enum type, in the order they are declared."); |
| values.setReturnType(new SimpleGroovyType(currentClassDoc.name() + "[]")); |
| currentClassDoc.add(values); |
| } |
| |
| private String extractImportPath(GroovySourceAST t) { |
| return recurseDownImportBranch(getPackageDotType(t)); |
| } |
| |
| private static GroovySourceAST getPackageDotType(GroovySourceAST t) { |
| GroovySourceAST child = t.childOfType(DOT); |
| if (child == null) { |
| child = t.childOfType(IDENT); |
| } |
| return child; |
| } |
| |
| private String recurseDownImportBranch(GroovySourceAST t) { |
| if (t != null) { |
| if (t.getType() == DOT) { |
| GroovySourceAST firstChild = (GroovySourceAST) t.getFirstChild(); |
| GroovySourceAST secondChild = (GroovySourceAST) firstChild.getNextSibling(); |
| return (recurseDownImportBranch(firstChild) + "/" + recurseDownImportBranch(secondChild)); |
| } |
| if (t.getType() == IDENT) { |
| return t.getText(); |
| } |
| if (t.getType() == STAR) { |
| return t.getText(); |
| } |
| } |
| return ""; |
| } |
| |
| private void addAnnotationRef(SimpleGroovyProgramElementDoc node, GroovySourceAST t) { |
| GroovySourceAST classNode = getPackageDotType(t); |
| if (classNode != null) { |
| node.addAnnotationRef(new SimpleGroovyAnnotationRef(extractName(classNode), getChildTextFromSource(t).trim())); |
| } |
| } |
| |
| private void addAnnotationRef(SimpleGroovyParameter node, GroovySourceAST t) { |
| GroovySourceAST classNode = getPackageDotType(t); |
| if (classNode != null) { |
| node.addAnnotationRef(new SimpleGroovyAnnotationRef(extractName(classNode), getChildTextFromSource(t).trim())); |
| } |
| } |
| |
| private void addAnnotationRefs(SimpleGroovyProgramElementDoc node, List<GroovySourceAST> nodes) { |
| for (GroovySourceAST t : nodes) { |
| addAnnotationRef(node, t); |
| } |
| } |
| |
| private void processAnnotations(GroovySourceAST t, SimpleGroovyProgramElementDoc node) { |
| GroovySourceAST modifiers = t.childOfType(MODIFIERS); |
| if (modifiers != null) { |
| addAnnotationRefs(node, modifiers.childrenOfType(ANNOTATION)); |
| } |
| } |
| |
| // hack warning! fragile! TODO find a better way |
| private String getDefaultValue(GroovySourceAST t) { |
| GroovySourceAST child = (GroovySourceAST) t.getFirstChild(); |
| if (t.getNumberOfChildren() != 4) return null; |
| for (int i = 1; i < t.getNumberOfChildren(); i++) { |
| child = (GroovySourceAST) child.getNextSibling(); |
| } |
| GroovySourceAST nodeToProcess = child; |
| if (child.getType() != ANNOTATION_ARRAY_INIT && child.getNumberOfChildren() > 0) { |
| nodeToProcess = (GroovySourceAST) child.getFirstChild(); |
| } |
| return getChildTextFromSource(nodeToProcess, ";"); |
| } |
| |
| private String getChildTextFromSource(GroovySourceAST child) { |
| return sourceBuffer.getSnippet( |
| new LineColumn(child.getLine(), child.getColumn()), |
| new LineColumn(child.getLineLast(), child.getColumnLast())); |
| } |
| |
| private String getChildTextFromSource(GroovySourceAST child, String tokens) { |
| String text = sourceBuffer.getSnippet( |
| new LineColumn(child.getLine(), child.getColumn()), |
| new LineColumn(child.getLine() + 1, 0)); |
| StringTokenizer st = new StringTokenizer(text, tokens); |
| return st.nextToken(); |
| } |
| |
| private boolean isFieldDefinition() { |
| GroovySourceAST parentNode = getParentNode(); |
| return parentNode != null && parentNode.getType() == OBJBLOCK; |
| } |
| |
| private boolean insideAnonymousInnerClass() { |
| GroovySourceAST grandParentNode = getGrandParentNode(); |
| return grandParentNode != null && grandParentNode.getType() == LITERAL_new; |
| } |
| |
| // return true if a property is found |
| private boolean processModifiers(GroovySourceAST t, SimpleGroovyAbstractableElementDoc memberOrClass) { |
| GroovySourceAST modifiers = t.childOfType(MODIFIERS); |
| boolean hasNonPublicVisibility = false; |
| boolean hasPublicVisibility = false; |
| if (modifiers != null) { |
| AST currentModifier = modifiers.getFirstChild(); |
| while (currentModifier != null) { |
| int type = currentModifier.getType(); |
| switch (type) { |
| case LITERAL_public: |
| memberOrClass.setPublic(true); |
| hasPublicVisibility = true; |
| break; |
| case LITERAL_protected: |
| memberOrClass.setProtected(true); |
| hasNonPublicVisibility = true; |
| break; |
| case LITERAL_private: |
| memberOrClass.setPrivate(true); |
| hasNonPublicVisibility = true; |
| break; |
| case LITERAL_static: |
| memberOrClass.setStatic(true); |
| break; |
| case FINAL: |
| memberOrClass.setFinal(true); |
| break; |
| case ABSTRACT: |
| memberOrClass.setAbstract(true); |
| break; |
| } |
| currentModifier = currentModifier.getNextSibling(); |
| } |
| if (!hasNonPublicVisibility && isGroovy && !(memberOrClass instanceof GroovyFieldDoc)) { |
| // in groovy, methods and classes are assumed public, unless informed otherwise |
| if (isPackageScope(modifiers)) { |
| memberOrClass.setPackagePrivate(true); |
| hasNonPublicVisibility = true; |
| } else { |
| memberOrClass.setPublic(true); |
| } |
| } else if (!hasNonPublicVisibility && !hasPublicVisibility && !isGroovy) { |
| if (insideInterface(memberOrClass) || insideAnnotationDef(memberOrClass)) { |
| memberOrClass.setPublic(true); |
| } else { |
| memberOrClass.setPackagePrivate(true); |
| } |
| } |
| if (memberOrClass instanceof GroovyFieldDoc && isGroovy && !hasNonPublicVisibility & !hasPublicVisibility) { |
| if (isPackageScope(modifiers)) { |
| memberOrClass.setPackagePrivate(true); |
| hasNonPublicVisibility = true; |
| } |
| } |
| if (memberOrClass instanceof GroovyFieldDoc && !hasNonPublicVisibility && !hasPublicVisibility && isGroovy) return true; |
| } else if (isGroovy && !(memberOrClass instanceof GroovyFieldDoc)) { |
| // in groovy, methods and classes are assumed public, unless informed otherwise |
| memberOrClass.setPublic(true); |
| } else if (!isGroovy) { |
| if (insideInterface(memberOrClass) || insideAnnotationDef(memberOrClass)) { |
| memberOrClass.setPublic(true); |
| } else { |
| memberOrClass.setPackagePrivate(true); |
| } |
| } |
| return memberOrClass instanceof GroovyFieldDoc && isGroovy && !hasNonPublicVisibility & !hasPublicVisibility; |
| } |
| |
| private boolean isPackageScope(GroovySourceAST modifiers) { |
| List<String> names = getAnnotationNames(modifiers); |
| return names.contains("groovy/transform/PackageScope") || names.contains("PackageScope"); |
| } |
| |
| private List<String> getAnnotationNames(GroovySourceAST modifiers) { |
| List<String> annotationNames = new ArrayList<String>(); |
| List<GroovySourceAST> annotations = modifiers.childrenOfType(ANNOTATION); |
| for (GroovySourceAST annotation : annotations) { |
| annotationNames.add(buildName((GroovySourceAST) annotation.getFirstChild())); |
| } |
| return annotationNames; |
| } |
| |
| private boolean insideInterface(SimpleGroovyAbstractableElementDoc memberOrClass) { |
| SimpleGroovyClassDoc current = getCurrentClassDoc(); |
| if (current == null || current == memberOrClass) return false; |
| return current.isInterface(); |
| } |
| |
| private boolean insideAnnotationDef(SimpleGroovyAbstractableElementDoc memberOrClass) { |
| SimpleGroovyClassDoc current = getCurrentClassDoc(); |
| if (current == null || current == memberOrClass) return false; |
| return current.isAnnotationType(); |
| } |
| |
| // todo - If no comment before node, then get comment from same node on parent class - ouch! |
| |
| private String getJavaDocCommentsBeforeNode(GroovySourceAST t) { |
| String result = ""; |
| LineColumn thisLineCol = new LineColumn(t.getLine(), t.getColumn()); |
| String text = sourceBuffer.getSnippet(lastLineCol, thisLineCol); |
| if (text != null) { |
| Matcher m = PREV_JAVADOC_COMMENT_PATTERN.matcher(text); |
| if (m.find()) { |
| result = m.group(1); |
| } |
| } |
| if (isMajorType(t)) { |
| lastLineCol = thisLineCol; |
| } |
| return result; |
| } |
| |
| private static boolean isMajorType(GroovySourceAST t) { |
| if (t == null) return false; |
| int tt = t.getType(); |
| return tt == CLASS_DEF || tt == TRAIT_DEF || tt == INTERFACE_DEF || tt == METHOD_DEF || tt == ANNOTATION_DEF || tt == ENUM_DEF || |
| tt == VARIABLE_DEF || tt == ANNOTATION_FIELD_DEF || tt == ENUM_CONSTANT_DEF || tt == CTOR_IDENT; |
| } |
| |
| private static String getText(GroovySourceAST node) { |
| String returnValue = null; |
| if (node != null) { |
| returnValue = node.getText(); |
| } |
| return returnValue; |
| } |
| |
| // preempt resolve as info is partially available here (aliases and star imports won't match here) |
| private String extractName(GroovySourceAST typeNode) { |
| String typeName = buildName(typeNode); |
| if (!typeName.contains("/")) { |
| String slashName = "/" + typeName; |
| // Groovy currently resolves this to last found so traverse in reverse order |
| for (int i = importedClassesAndPackages.size() - 1; i >= 0; i--) { |
| String name = importedClassesAndPackages.get(i); |
| if (!aliases.containsValue(name) && name.endsWith(slashName)) { |
| typeName = name; |
| break; |
| } |
| } |
| } |
| return typeName; |
| } |
| |
| private String buildName(GroovySourceAST t) { |
| if (t != null) { |
| if (t.getType() == DOT) { |
| GroovySourceAST firstChild = (GroovySourceAST) t.getFirstChild(); |
| GroovySourceAST secondChild = (GroovySourceAST) firstChild.getNextSibling(); |
| return (buildName(firstChild) + "/" + buildName(secondChild)); |
| } |
| if (t.getType() == IDENT) { |
| return t.getText(); |
| } |
| } |
| return ""; |
| } |
| |
| private String getTypeOrDefault(GroovySourceAST t) { |
| GroovySourceAST typeNode = t.childOfType(TYPE); |
| return getTypeNodeAsText(typeNode, "def"); |
| } |
| |
| private String getTypeNodeAsText(GroovySourceAST typeNode, String defaultText) { |
| // TODO refactor to retain richer type information rather than converting to String |
| if (typeNode == null) { |
| return defaultText; |
| } |
| if (typeNode.getType() == TYPE) { |
| return getAsText(typeNode, defaultText); |
| } else if (typeNode.getType() == TYPE_ARGUMENT) { |
| return getTypeNodeAsText((GroovySourceAST) typeNode.getFirstChild(), defaultText); |
| } else if (typeNode.getType() == WILDCARD_TYPE) { |
| AST next = typeNode.getNextSibling(); |
| if (next == null && typeNode.getFirstChild() != null) { |
| // Java2Groovy produces a slightly different tree structure (TODO fix converter or java.g instead?) |
| next = typeNode.getFirstChild(); |
| } |
| if (next == null) return "?"; |
| String boundType = getTypeNodeAsText((GroovySourceAST) next.getFirstChild(), defaultText); |
| if (next.getType() == TYPE_UPPER_BOUNDS) return "? extends " + boundType; |
| if (next.getType() == TYPE_LOWER_BOUNDS) return "? super " + boundType; |
| } else if (typeNode.getType() == IDENT) { |
| String ident = getAsTextCurrent(typeNode, defaultText); |
| AST next = typeNode.getNextSibling(); |
| if (next == null && typeNode.getFirstChild() != null) { |
| // Java2Groovy produces a slightly different tree structure (TODO fix converter or java.g instead?) |
| next = typeNode.getFirstChild(); |
| } |
| if (next == null) return ident; |
| String boundType = getTypeNodeAsText((GroovySourceAST) next.getFirstChild(), defaultText); |
| if (next.getType() == TYPE_UPPER_BOUNDS) return ident + " extends " + boundType; |
| if (next.getType() == TYPE_LOWER_BOUNDS) return ident + " super " + boundType; |
| } |
| return defaultText; |
| } |
| |
| private String getAsText(GroovySourceAST typeNode, String defaultText) { |
| GroovySourceAST child = (GroovySourceAST) typeNode.getFirstChild(); |
| return getAsTextCurrent(child, defaultText); |
| } |
| |
| private String getAsTextCurrent(GroovySourceAST node, String defaultText) { |
| if (node == null) return defaultText; |
| switch (node.getType()) { |
| // literals |
| case LITERAL_boolean: |
| return "boolean"; |
| case LITERAL_byte: |
| return "byte"; |
| case LITERAL_char: |
| return "char"; |
| // note: LITERAL_def never created |
| case LITERAL_double: |
| return "double"; |
| case LITERAL_float: |
| return "float"; |
| case LITERAL_int: |
| return "int"; |
| case LITERAL_long: |
| return "long"; |
| case LITERAL_short: |
| return "short"; |
| case LITERAL_void: |
| return "void"; |
| case ARRAY_DECLARATOR: |
| String componentType = getAsText(node, defaultText); |
| if (!componentType.equals("def")) return componentType + "[]"; |
| return "java/lang/Object[]"; |
| // identifiers |
| case IDENT: |
| StringBuilder ident = new StringBuilder(); |
| ident.append(node.getText()); |
| GroovySourceAST identChild = (GroovySourceAST) node.getFirstChild(); |
| getTypeArguments(identChild, ident, defaultText); |
| return ident.toString(); |
| case DOT: |
| StringBuilder dot = new StringBuilder(); |
| GroovySourceAST dotChild = (GroovySourceAST) node.getFirstChild(); |
| while (dotChild != null) { |
| if (dotChild.getType() == IDENT || dotChild.getType() == DOT) { |
| if (dot.length() > 0) dot.append("/"); |
| dot.append(getAsTextCurrent(dotChild, defaultText)); |
| } else if (dotChild.getType() == TYPE_ARGUMENTS) { |
| getTypeArguments(dotChild, dot, defaultText); |
| } |
| dotChild = (GroovySourceAST) dotChild.getNextSibling(); |
| } |
| return dot.toString(); |
| } |
| return defaultText; |
| } |
| |
| private void getTypeArguments(GroovySourceAST child, StringBuilder result, String defaultText) { |
| if (child != null && child.getType() == TYPE_ARGUMENTS && child.getNumberOfChildren() > 0) { |
| result.append("<"); |
| GroovySourceAST typeArgumentsNext = (GroovySourceAST) child.getFirstChild(); |
| List<String> typeArgumentParts = new ArrayList<String>(); |
| while (typeArgumentsNext != null) { |
| if (typeArgumentsNext.getType() == TYPE_ARGUMENT && typeArgumentsNext.getNumberOfChildren() > 0) { |
| typeArgumentParts.add(getTypeNodeAsText((GroovySourceAST) typeArgumentsNext.getFirstChild(), defaultText)); |
| } |
| typeArgumentsNext = (GroovySourceAST) typeArgumentsNext.getNextSibling(); |
| } |
| result.append(DefaultGroovyMethods.join((Iterable)typeArgumentParts, ", ")); |
| result.append(">"); |
| } |
| } |
| |
| private void getTypeParameters(GroovySourceAST child, StringBuilder result, String defaultText) { |
| if (child != null && child.getType() == TYPE_PARAMETERS && child.getNumberOfChildren() > 0) { |
| result.append("<"); |
| GroovySourceAST typeParametersNext = (GroovySourceAST) child.getFirstChild(); |
| List<String> typeParameterParts = new ArrayList<String>(); |
| while (typeParametersNext != null) { |
| if (typeParametersNext.getType() == TYPE_PARAMETER && typeParametersNext.getNumberOfChildren() > 0) { |
| typeParameterParts.add(getTypeNodeAsText((GroovySourceAST) typeParametersNext.getFirstChild(), defaultText)); |
| } |
| typeParametersNext = (GroovySourceAST) typeParametersNext.getNextSibling(); |
| } |
| result.append(DefaultGroovyMethods.join((Iterable)typeParameterParts, ", ")); |
| result.append(">"); |
| } |
| } |
| |
| private void addParametersTo(GroovySourceAST t, SimpleGroovyExecutableMemberDoc executableMemberDoc) { |
| // parameters |
| GroovySourceAST parametersNode = t.childOfType(PARAMETERS); |
| if (parametersNode != null && parametersNode.getNumberOfChildren() > 0) { |
| GroovySourceAST currentNode = (GroovySourceAST) parametersNode.getFirstChild(); |
| while (currentNode != null) { |
| String parameterTypeName = getTypeOrDefault(currentNode); |
| String parameterName = getText(currentNode.childOfType(IDENT)); |
| SimpleGroovyParameter parameter = new SimpleGroovyParameter(parameterName); |
| parameter.setVararg(currentNode.getType() == VARIABLE_PARAMETER_DEF); |
| parameter.setTypeName(parameterTypeName); |
| GroovySourceAST modifiers = currentNode.childOfType(MODIFIERS); |
| if (modifiers != null) { |
| List<GroovySourceAST> annotations = modifiers.childrenOfType(ANNOTATION); |
| for (GroovySourceAST a : annotations) { |
| addAnnotationRef(parameter, a); |
| } |
| } |
| executableMemberDoc.add(parameter); |
| if (currentNode.getNumberOfChildren() == 4) { |
| handleDefaultValue(currentNode, parameter); |
| } |
| currentNode = (GroovySourceAST) currentNode.getNextSibling(); |
| } |
| } |
| } |
| |
| private void handleDefaultValue(GroovySourceAST currentNode, SimpleGroovyParameter parameter) { |
| GroovySourceAST paramPart = (GroovySourceAST) currentNode.getFirstChild(); |
| for (int i = 1; i < currentNode.getNumberOfChildren(); i++) { |
| paramPart = (GroovySourceAST) paramPart.getNextSibling(); |
| } |
| GroovySourceAST nodeToProcess = paramPart; |
| if (paramPart.getNumberOfChildren() > 0) { |
| nodeToProcess = (GroovySourceAST) paramPart.getFirstChild(); |
| } |
| // hack warning! |
| // TODO handle , and ) when they occur within Strings |
| parameter.setDefaultValue(getChildTextFromSource(nodeToProcess, ",)")); |
| } |
| |
| public void push(GroovySourceAST t) { |
| stack.push(t); |
| } |
| |
| public GroovySourceAST pop() { |
| if (!stack.empty()) { |
| return stack.pop(); |
| } |
| return null; |
| } |
| |
| private GroovySourceAST getParentNode() { |
| GroovySourceAST parentNode = null; |
| GroovySourceAST currentNode = stack.pop(); |
| if (!stack.empty()) { |
| parentNode = stack.peek(); |
| } |
| stack.push(currentNode); |
| return parentNode; |
| } |
| |
| private GroovySourceAST getGrandParentNode() { |
| GroovySourceAST grandParentNode = null; |
| GroovySourceAST parentNode; |
| GroovySourceAST currentNode = stack.pop(); |
| if (!stack.empty()) { |
| parentNode = stack.pop(); |
| if (!stack.empty()) { |
| grandParentNode = stack.peek(); |
| } |
| stack.push(parentNode); |
| } |
| stack.push(currentNode); |
| return grandParentNode; |
| } |
| |
| private SimpleGroovyClassDoc getCurrentOrTopLevelClassDoc(GroovySourceAST node) { |
| SimpleGroovyClassDoc current = getCurrentClassDoc(); |
| if (current != null) return current; |
| return foundClasses.get(getIdentFor(node)); |
| } |
| |
| private SimpleGroovyClassDoc getCurrentClassDoc() { |
| if (stack.isEmpty()) return null; |
| GroovySourceAST node = getParentNode(); |
| if (isTopLevelConstruct(node) && foundClasses != null) { |
| return foundClasses.get(getIdentFor(node)); |
| } |
| GroovySourceAST saved = stack.pop(); |
| SimpleGroovyClassDoc result = getCurrentClassDoc(); |
| stack.push(saved); |
| return result; |
| } |
| |
| private static String getIdentFor(GroovySourceAST gpn) { |
| GroovySourceAST ident = gpn.childOfType(IDENT); |
| return ident == null ? "" : ident.getText(); |
| } |
| |
| private String getIdentPlusTypeArgsFor(GroovySourceAST gpn) { |
| GroovySourceAST groovySourceAST = gpn.childOfType(IDENT); |
| StringBuilder ident = new StringBuilder(); |
| ident.append(groovySourceAST.getText()); |
| GroovySourceAST typeParams = (GroovySourceAST) groovySourceAST.getNextSibling(); |
| getTypeParameters(typeParams, ident, "def"); |
| return ident.toString(); |
| } |
| } |