diff options
Diffstat (limited to 'base/vim/bundle/javacomplete/autoload/javacomplete.vim')
-rw-r--r-- | base/vim/bundle/javacomplete/autoload/javacomplete.vim | 2932 |
1 files changed, 2932 insertions, 0 deletions
diff --git a/base/vim/bundle/javacomplete/autoload/javacomplete.vim b/base/vim/bundle/javacomplete/autoload/javacomplete.vim new file mode 100644 index 0000000..adda8a3 --- /dev/null +++ b/base/vim/bundle/javacomplete/autoload/javacomplete.vim @@ -0,0 +1,2932 @@ +" Vim completion script - hit 80% complete tasks +" Version: 0.77.1.2 +" Language: Java +" Maintainer: cheng fang <fangread@yahoo.com.cn> +" Last Change: 2011-01-30 +" Copyright: Copyright (C) 2006-2007 cheng fang. All rights reserved. +" License: Vim License (see vim's :help license) + + +" constants {{{1 +" input context type +let s:CONTEXT_AFTER_DOT = 1 +let s:CONTEXT_METHOD_PARAM = 2 +let s:CONTEXT_IMPORT = 3 +let s:CONTEXT_IMPORT_STATIC = 4 +let s:CONTEXT_PACKAGE_DECL = 6 +let s:CONTEXT_NEED_TYPE = 7 +let s:CONTEXT_OTHER = 0 + + +let s:ARRAY_TYPE_MEMBERS = [ +\ {'kind': 'm', 'word': 'clone(', 'abbr': 'clone()', 'menu': 'Object clone()', }, +\ {'kind': 'm', 'word': 'equals(', 'abbr': 'equals()', 'menu': 'boolean equals(Object)', }, +\ {'kind': 'm', 'word': 'getClass(', 'abbr': 'getClass()', 'menu': 'Class Object.getClass()', }, +\ {'kind': 'm', 'word': 'hashCode(', 'abbr': 'hashCode()', 'menu': 'int hashCode()', }, +\ {'kind': 'f', 'word': 'length', 'menu': 'int'}, +\ {'kind': 'm', 'word': 'notify(', 'abbr': 'notify()', 'menu': 'void Object.notify()', }, +\ {'kind': 'm', 'word': 'notifyAll(', 'abbr': 'notifyAll()', 'menu': 'void Object.notifyAll()', }, +\ {'kind': 'm', 'word': 'toString(', 'abbr': 'toString()', 'menu': 'String toString()', }, +\ {'kind': 'm', 'word': 'wait(', 'abbr': 'wait()', 'menu': 'void Object.wait() throws InterruptedException', }, +\ {'kind': 'm', 'dup': 1, 'word': 'wait(', 'abbr': 'wait()', 'menu': 'void Object.wait(long timeout) throws InterruptedException', }, +\ {'kind': 'm', 'dup': 1, 'word': 'wait(', 'abbr': 'wait()', 'menu': 'void Object.wait(long timeout, int nanos) throws InterruptedException', }] + +let s:ARRAY_TYPE_INFO = {'tag': 'CLASSDEF', 'name': '[', 'ctors': [], +\ 'fields': [{'n': 'length', 'm': '1', 't': 'int'}], +\ 'methods':[ +\ {'n': 'clone', 'm': '1', 'r': 'Object', 'p': [], 'd': 'Object clone()'}, +\ {'n': 'equals', 'm': '1', 'r': 'boolean', 'p': ['Object'], 'd': 'boolean Object.equals(Object obj)'}, +\ {'n': 'getClass', 'm': '100010001', 'r': 'Class', 'p': [], 'd': 'Class Object.getClass()'}, +\ {'n': 'hashCode', 'm': '100000001', 'r': 'int', 'p': [], 'd': 'int Object.hashCode()'}, +\ {'n': 'notify', 'm': '100010001', 'r': 'void', 'p': [], 'd': 'void Object.notify()'}, +\ {'n': 'notifyAll','m': '100010001', 'r': 'void', 'p': [], 'd': 'void Object.notifyAll()'}, +\ {'n': 'toString', 'm': '1', 'r': 'String', 'p': [], 'd': 'String Object.toString()'}, +\ {'n': 'wait', 'm': '10001', 'r': 'void', 'p': [], 'd': 'void Object.wait() throws InterruptedException'}, +\ {'n': 'wait', 'm': '100010001', 'r': 'void', 'p': ['long'], 'd': 'void Object.wait(long timeout) throws InterruptedException'}, +\ {'n': 'wait', 'm': '10001', 'r': 'void', 'p': ['long','int'], 'd': 'void Object.wait(long timeout, int nanos) throws InterruptedException'}, +\ ]} + +let s:PRIMITIVE_TYPE_INFO = {'tag': 'CLASSDEF', 'name': '!', 'fields': [{'n': 'class','m': '1','t': 'Class'}]} + +let s:JSP_BUILTIN_OBJECTS = {'session': 'javax.servlet.http.HttpSession', +\ 'request': 'javax.servlet.http.HttpServletRequest', +\ 'response': 'javax.servlet.http.HttpServletResponse', +\ 'pageContext': 'javax.servlet.jsp.PageContext', +\ 'application': 'javax.servlet.ServletContext', +\ 'config': 'javax.servlet.ServletConfig', +\ 'out': 'javax.servlet.jsp.JspWriter', +\ 'page': 'javax.servlet.jsp.HttpJspPage', } + + +let s:PRIMITIVE_TYPES = ['boolean', 'byte', 'char', 'int', 'short', 'long', 'float', 'double'] +let s:KEYWORDS_MODS = ['public', 'private', 'protected', 'static', 'final', 'synchronized', 'volatile', 'transient', 'native', 'strictfp', 'abstract'] +let s:KEYWORDS_TYPE = ['class', 'interface', 'enum'] +let s:KEYWORDS = s:PRIMITIVE_TYPES + s:KEYWORDS_MODS + s:KEYWORDS_TYPE + ['super', 'this', 'void'] + ['assert', 'break', 'case', 'catch', 'const', 'continue', 'default', 'do', 'else', 'extends', 'finally', 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'interface', 'new', 'package', 'return', 'switch', 'throw', 'throws', 'try', 'while', 'true', 'false', 'null'] + +let s:PATH_SEP = ':' +let s:FILE_SEP = '/' +if has("win32") || has("win64") || has("win16") || has("dos32") || has("dos16") + let s:PATH_SEP = ';' + let s:FILE_SEP = '\' +endif + +let s:RE_BRACKETS = '\%(\s*\[\s*\]\)' +let s:RE_IDENTIFIER = '[a-zA-Z_$][a-zA-Z0-9_$]*' +let s:RE_QUALID = s:RE_IDENTIFIER. '\%(\s*\.\s*' .s:RE_IDENTIFIER. '\)*' + +let s:RE_REFERENCE_TYPE = s:RE_QUALID . s:RE_BRACKETS . '*' +let s:RE_TYPE = s:RE_REFERENCE_TYPE + +let s:RE_TYPE_ARGUMENT = '\%(?\s\+\%(extends\|super\)\s\+\)\=' . s:RE_TYPE +let s:RE_TYPE_ARGUMENTS = '<' . s:RE_TYPE_ARGUMENT . '\%(\s*,\s*' . s:RE_TYPE_ARGUMENT . '\)*>' +let s:RE_TYPE_WITH_ARGUMENTS_I = s:RE_IDENTIFIER . '\s*' . s:RE_TYPE_ARGUMENTS +let s:RE_TYPE_WITH_ARGUMENTS = s:RE_TYPE_WITH_ARGUMENTS_I . '\%(\s*' . s:RE_TYPE_WITH_ARGUMENTS_I . '\)*' + +let s:RE_TYPE_MODS = '\%(public\|protected\|private\|abstract\|static\|final\|strictfp\)' +let s:RE_TYPE_DECL_HEAD = '\(class\|interface\|enum\)[ \t\n\r]\+' +let s:RE_TYPE_DECL = '\<\C\(\%(' .s:RE_TYPE_MODS. '\s\+\)*\)' .s:RE_TYPE_DECL_HEAD. '\(' .s:RE_IDENTIFIER. '\)[< \t\n\r]' + +let s:RE_ARRAY_TYPE = '^\s*\(' .s:RE_QUALID . '\)\(' . s:RE_BRACKETS . '\+\)\s*$' +let s:RE_SELECT_OR_ACCESS = '^\s*\(' . s:RE_IDENTIFIER . '\)\s*\(\[.*\]\)\=\s*$' +let s:RE_ARRAY_ACCESS = '^\s*\(' . s:RE_IDENTIFIER . '\)\s*\(\[.*\]\)\+\s*$' +let s:RE_CASTING = '^\s*(\(' .s:RE_QUALID. '\))\s*\(' . s:RE_IDENTIFIER . '\)\>' + +let s:RE_KEYWORDS = '\<\%(' . join(s:KEYWORDS, '\|') . '\)\>' + + +" local variables {{{1 +let b:context_type = s:CONTEXT_OTHER +"let b:statement = '' " statement before cursor +let b:dotexpr = '' " expression ends with '.' +let b:incomplete = '' " incomplete word: 1. dotexpr.method(|) 2. new classname(|) 3. dotexpr.ab|, 4. ja|, 5. method(| +let b:errormsg = '' + +" script variables {{{1 +let s:cache = {} " FQN -> member list, e.g. {'java.lang.StringBuffer': classinfo, 'java.util': packageinfo, '/dir/TopLevelClass.java': compilationUnit} +let s:files = {} " srouce file path -> properties, e.g. {filekey: {'unit': compilationUnit, 'changedtick': tick, }} +let s:history = {} " + + +" This function is used for the 'omnifunc' option. {{{1 +function! javacomplete#Complete(findstart, base) + if a:findstart + let s:et_whole = reltime() + let start = col('.') - 1 + let s:log = [] + + " reset enviroment + let b:dotexpr = '' + let b:incomplete = '' + let b:context_type = s:CONTEXT_OTHER + + let statement = s:GetStatement() + call s:WatchVariant('statement: "' . statement . '"') + + if statement =~ '[.0-9A-Za-z_]\s*$' + let valid = 1 + if statement =~ '\.\s*$' + let valid = statement =~ '[")0-9A-Za-z_\]]\s*\.\s*$' && statement !~ '\<\H\w\+\.\s*$' && statement !~ '\<\(abstract\|assert\|break\|case\|catch\|const\|continue\|default\|do\|else\|enum\|extends\|final\|finally\|for\|goto\|if\|implements\|import\|instanceof\|interface\|native\|new\|package\|private\|protected\|public\|return\|static\|strictfp\|switch\|synchronized\|throw\|throws\|transient\|try\|volatile\|while\|true\|false\|null\)\.\s*$' + endif + if !valid + return -1 + endif + + let b:context_type = s:CONTEXT_AFTER_DOT + + " import or package declaration + if statement =~# '^\s*\(import\|package\)\s\+' + let statement = substitute(statement, '\s\+\.', '.', 'g') + let statement = substitute(statement, '\.\s\+', '.', 'g') + if statement =~ '^\s*import\s\+' + let b:context_type = statement =~# '\<static\s\+' ? s:CONTEXT_IMPORT_STATIC : s:CONTEXT_IMPORT + let b:dotexpr = substitute(statement, '^\s*import\s\+\(static\s\+\)\?', '', '') + else + let b:context_type = s:CONTEXT_PACKAGE_DECL + let b:dotexpr = substitute(statement, '\s*package\s\+', '', '') + endif + + " String literal + elseif statement =~ '"\s*\.\s*$' + let b:dotexpr = substitute(statement, '\s*\.\s*$', '\.', '') + return start - strlen(b:incomplete) + + else + " type declaration NOTE: not supported generic yet. + let idx_type = matchend(statement, '^\s*' . s:RE_TYPE_DECL) + if idx_type != -1 + let b:dotexpr = strpart(statement, idx_type) + " return if not after extends or implements + if b:dotexpr !~ '^\(extends\|implements\)\s\+' + return -1 + endif + let b:context_type = s:CONTEXT_NEED_TYPE + endif + + let b:dotexpr = s:ExtractCleanExpr(statement) + endif + + " all cases: " java.ut|" or " java.util.|" or "ja|" + let b:incomplete = strpart(b:dotexpr, strridx(b:dotexpr, '.')+1) + let b:dotexpr = strpart(b:dotexpr, 0, strridx(b:dotexpr, '.')+1) + return start - strlen(b:incomplete) + + + " method parameters, treat methodname or 'new' as an incomplete word + elseif statement =~ '(\s*$' + " TODO: Need to exclude method declaration? + let b:context_type = s:CONTEXT_METHOD_PARAM + let pos = strridx(statement, '(') + let s:padding = strpart(statement, pos+1) + let start = start - (len(statement) - pos) + + let statement = substitute(statement, '\s*(\s*$', '', '') + + " new ClassName? + let str = matchstr(statement, '\<new\s\+' . s:RE_QUALID . '$') + if str != '' + let str = substitute(str, '^new\s\+', '', '') + if !s:IsKeyword(str) + let b:incomplete = '+' + let b:dotexpr = str + return start - len(b:dotexpr) + endif + + " normal method invocations + else + let pos = match(statement, '\s*' . s:RE_IDENTIFIER . '$') + " case: "method(|)", "this(|)", "super(|)" + if pos == 0 + let statement = substitute(statement, '^\s*', '', '') + " treat "this" or "super" as a type name. + if statement == 'this' || statement == 'super' + let b:dotexpr = statement + let b:incomplete = '+' + return start - len(b:dotexpr) + + elseif !s:IsKeyword(statement) + let b:incomplete = statement + return start - strlen(b:incomplete) + endif + + " case: "expr.method(|)" + elseif statement[pos-1] == '.' && !s:IsKeyword(strpart(statement, pos)) + let b:dotexpr = s:ExtractCleanExpr(strpart(statement, 0, pos)) + let b:incomplete = strpart(statement, pos) + return start - strlen(b:incomplete) + endif + endif + endif + + return -1 + endif + + + " Return list of matches. + + call s:WatchVariant('b:context_type: "' . b:context_type . '" b:incomplete: "' . b:incomplete . '" b:dotexpr: "' . b:dotexpr . '"') + if b:dotexpr =~ '^\s*$' && b:incomplete =~ '^\s*$' + return [] + endif + + + let result = [] + if b:dotexpr !~ '^\s*$' + if b:context_type == s:CONTEXT_AFTER_DOT + let result = s:CompleteAfterDot(b:dotexpr) + elseif b:context_type == s:CONTEXT_IMPORT || b:context_type == s:CONTEXT_IMPORT_STATIC || b:context_type == s:CONTEXT_PACKAGE_DECL || b:context_type == s:CONTEXT_NEED_TYPE + let result = s:GetMembers(b:dotexpr[:-2]) + elseif b:context_type == s:CONTEXT_METHOD_PARAM + if b:incomplete == '+' + let result = s:GetConstructorList(b:dotexpr) + else + let result = s:CompleteAfterDot(b:dotexpr) + endif + endif + + " only incomplete word + elseif b:incomplete !~ '^\s*$' + " only need methods + if b:context_type == s:CONTEXT_METHOD_PARAM + let methods = s:SearchForName(b:incomplete, 0, 1)[1] + call extend(result, eval('[' . s:DoGetMethodList(methods) . ']')) + + else + let result = s:CompleteAfterWord(b:incomplete) + endif + + " then no filter needed + let b:incomplete = '' + endif + + + if len(result) > 0 + " filter according to b:incomplete + if len(b:incomplete) > 0 && b:incomplete != '+' + let result = filter(result, "type(v:val) == type('') ? v:val =~ '^" . b:incomplete . "' : v:val['word'] =~ '^" . b:incomplete . "'") + endif + + if exists('s:padding') && !empty(s:padding) + for item in result + if type(item) == type("") + let item .= s:padding + else + let item.word .= s:padding + endif + endfor + unlet s:padding + endif + + call s:Debug('finish completion' . reltimestr(reltime(s:et_whole)) . 's') + return result + endif + + if strlen(b:errormsg) > 0 + echoerr 'javacomplete error: ' . b:errormsg + let b:errormsg = '' + endif +endfunction + +" Precondition: incomplete must be a word without '.'. +" return all the matched, variables, fields, methods, types, packages +fu! s:CompleteAfterWord(incomplete) + " packages in jar files + if !exists('s:all_packages_in_jars_loaded') + call s:DoGetInfoByReflection('-', '-P') + let s:all_packages_in_jars_loaded = 1 + endif + + let pkgs = [] + let types = [] + for key in keys(s:cache) + if key =~# '^' . a:incomplete + if type(s:cache[key]) == type('') || get(s:cache[key], 'tag', '') == 'PACKAGE' + call add(pkgs, {'kind': 'P', 'word': key}) + + " filter out type info + elseif b:context_type != s:CONTEXT_PACKAGE_DECL && b:context_type != s:CONTEXT_IMPORT && b:context_type != s:CONTEXT_IMPORT_STATIC + call add(types, {'kind': 'C', 'word': key}) + endif + endif + endfor + + let pkgs += s:DoGetPackageInfoInDirs(a:incomplete, b:context_type == s:CONTEXT_PACKAGE_DECL, 1) + + + " add accessible types which name beginning with the incomplete in source files + " TODO: remove the inaccessible + if b:context_type != s:CONTEXT_PACKAGE_DECL + " single type import + for fqn in s:GetImports('imports_fqn') + let name = fqn[strridx(fqn, ".")+1:] + if name =~ '^' . a:incomplete + call add(types, {'kind': 'C', 'word': name}) + endif + endfor + + " current file + let lnum_old = line('.') + let col_old = col('.') + call cursor(1, 1) + while 1 + let lnum = search('\<\C\(class\|interface\|enum\)[ \t\n\r]\+' . a:incomplete . '[a-zA-Z0-9_$]*[< \t\n\r]', 'W') + if lnum == 0 + break + elseif s:InCommentOrLiteral(line('.'), col('.')) + continue + else + normal w + call add(types, {'kind': 'C', 'word': matchstr(getline(line('.'))[col('.')-1:], s:RE_IDENTIFIER)}) + endif + endwhile + call cursor(lnum_old, col_old) + + " other files + let filepatterns = '' + for dirpath in s:GetSourceDirs(expand('%:p')) + let filepatterns .= escape(dirpath, ' \') . '/*.java ' + endfor + exe 'vimgrep /\s*' . s:RE_TYPE_DECL . '/jg ' . filepatterns + for item in getqflist() + if item.text !~ '^\s*\*\s\+' + let text = matchstr(s:Prune(item.text, -1), '\s*' . s:RE_TYPE_DECL) + if text != '' + let subs = split(substitute(text, '\s*' . s:RE_TYPE_DECL, '\1;\2;\3', ''), ';', 1) + if subs[2] =~# '^' . a:incomplete && (subs[0] =~ '\C\<public\>' || fnamemodify(bufname(item.bufnr), ':p:h') == expand('%:p:h')) + call add(types, {'kind': 'C', 'word': subs[2]}) + endif + endif + endif + endfor + endif + + + let result = [] + + " add variables and members in source files + if b:context_type == s:CONTEXT_AFTER_DOT + let matches = s:SearchForName(a:incomplete, 0, 0) + let result += sort(eval('[' . s:DoGetFieldList(matches[2]) . ']')) + let result += sort(eval('[' . s:DoGetMethodList(matches[1]) . ']')) + endif + let result += sort(pkgs) + let result += sort(types) + + return result +endfu + + +" Precondition: expr must end with '.' +" return members of the value of expression +function! s:CompleteAfterDot(expr) + let items = s:ParseExpr(a:expr) " TODO: return a dict containing more than items + if empty(items) + return [] + endif + + + " 0. String literal + call s:Info('P0. "str".|') + if items[-1] =~ '"$' + return s:GetMemberList("java.lang.String") + endif + + + let ti = {} + let ii = 1 " item index + let itemkind = 0 + + " + " optimized process + " + " search the longest expr consisting of ident + let i = 1 + let k = i + while i < len(items) && items[i] =~ '^\s*' . s:RE_IDENTIFIER . '\s*$' + let ident = substitute(items[i], '\s', '', 'g') + if ident == 'class' || ident == 'this' || ident == 'super' + let k = i + " return when found other keywords + elseif s:IsKeyword(ident) + return [] + endif + let items[i] = substitute(items[i], '\s', '', 'g') + let i += 1 + endwhile + + if i > 1 + " cases: "this.|", "super.|", "ClassName.this.|", "ClassName.super.|", "TypeName.class.|" + if items[k] ==# 'class' || items[k] ==# 'this' || items[k] ==# 'super' + call s:Info('O1. ' . items[k] . ' ' . join(items[:k-1], '.')) + let ti = s:DoGetClassInfo(items[k] == 'class' ? 'java.lang.Class' : join(items[:k-1], '.')) + if !empty(ti) + let itemkind = items[k] ==# 'this' ? 1 : items[k] ==# 'super' ? 2 : 0 + let ii = k+1 + else + return [] + endif + + " case: "java.io.File.|" + else + let fqn = join(items[:i-1], '.') + let srcpath = join(s:GetSourceDirs(expand('%:p'), s:GetPackageName()), ',') + call s:Info('O2. ' . fqn) + call s:DoGetTypeInfoForFQN([fqn], srcpath) + if get(get(s:cache, fqn, {}), 'tag', '') == 'CLASSDEF' + let ti = s:cache[fqn] + let itemkind = 11 + let ii = i + endif + endif + endif + + + " + " first item + " + if empty(ti) + " cases: + " 1) "int.|", "void.|" - primitive type or pseudo-type, return `class` + " 2) "this.|", "super.|" - special reference + " 3) "var.|" - variable or field + " 4) "String.|" - type imported or defined locally + " 5) "java.|" - package + if items[0] =~ '^\s*' . s:RE_IDENTIFIER . '\s*$' + let ident = substitute(items[0], '\s', '', 'g') + + if s:IsKeyword(ident) + " 1) + call s:Info('F1. "' . ident . '.|"') + if ident ==# 'void' || s:IsBuiltinType(ident) + let ti = s:PRIMITIVE_TYPE_INFO + let itemkind = 11 + + " 2) + call s:Info('F2. "' . ident . '.|"') + elseif ident ==# 'this' || ident ==# 'super' + let itemkind = ident ==# 'this' ? 1 : ident ==# 'super' ? 2 : 0 + let ti = s:DoGetClassInfo(ident) + endif + + else + " 3) + let typename = s:GetDeclaredClassName(ident) + call s:Info('F3. "' . ident . '.|" typename: "' . typename . '"') + if (typename != '') + if typename[0] == '[' || typename[-1:] == ']' + let ti = s:ARRAY_TYPE_INFO + elseif typename != 'void' && !s:IsBuiltinType(typename) + let ti = s:DoGetClassInfo(typename) + endif + + else + " 4) + call s:Info('F4. "TypeName.|"') + let ti = s:DoGetClassInfo(ident) + let itemkind = 11 + + if get(ti, 'tag', '') != 'CLASSDEF' + let ti = {} + endif + + " 5) + if empty(ti) + call s:Info('F5. "package.|"') + unlet ti + let ti = s:GetMembers(ident) " s:DoGetPackegInfo(ident) + let itemkind = 20 + endif + endif + endif + + " method invocation: "method().|" - "this.method().|" + elseif items[0] =~ '^\s*' . s:RE_IDENTIFIER . '\s*(' + let ti = s:MethodInvocation(items[0], ti, itemkind) + + " array type, return `class`: "int[] [].|", "java.lang.String[].|", "NestedClass[].|" + elseif items[0] =~# s:RE_ARRAY_TYPE + call s:Info('array type. "' . items[0] . '"') + let qid = substitute(items[0], s:RE_ARRAY_TYPE, '\1', '') + if s:IsBuiltinType(qid) || (!s:HasKeyword(qid) && !empty(s:DoGetClassInfo(qid))) + let ti = s:PRIMITIVE_TYPE_INFO + let itemkind = 11 + endif + + " class instance creation expr: "new String().|", "new NonLoadableClass().|" + " array creation expr: "new int[i=1] [val()].|", "new java.lang.String[].|" + elseif items[0] =~ '^\s*new\s\+' + call s:Info('creation expr. "' . items[0] . '"') + let subs = split(substitute(items[0], '^\s*new\s\+\(' .s:RE_QUALID. '\)\s*\([([]\)', '\1;\2', ''), ';') + if subs[1][0] == '[' + let ti = s:ARRAY_TYPE_INFO + elseif subs[1][0] == '(' + let ti = s:DoGetClassInfo(subs[0]) + " exclude interfaces and abstract class. TODO: exclude the inaccessible + if get(ti, 'flags', '')[-10:-10] || get(ti, 'flags', '')[-11:-11] + echo 'cannot instantiate the type ' . subs[0] + let ti = {} + return [] + endif + endif + + " casting conversion: "(Object)o.|" + elseif items[0] =~ s:RE_CASTING + call s:Info('Casting conversion. "' . items[0] . '"') + let subs = split(substitute(items[0], s:RE_CASTING, '\1;\2', ''), ';') + let ti = s:DoGetClassInfo(subs[0]) + + " array access: "var[i][j].|" Note: "var[i][]" is incorrect + elseif items[0] =~# s:RE_ARRAY_ACCESS + let subs = split(substitute(items[0], s:RE_ARRAY_ACCESS, '\1;\2', ''), ';') + if get(subs, 1, '') !~ s:RE_BRACKETS + let typename = s:GetDeclaredClassName(subs[0]) + call s:Info('ArrayAccess. "' .items[0]. '.|" typename: "' . typename . '"') + if (typename != '') + let ti = s:ArrayAccess(typename, items[0]) + endif + endif + endif + endif + + + " + " next items + " + while !empty(ti) && ii < len(items) + " method invocation: "PrimaryExpr.method(parameters)[].|" + if items[ii] =~ '^\s*' . s:RE_IDENTIFIER . '\s*(' + let ti = s:MethodInvocation(items[ii], ti, itemkind) + let itemkind = 0 + let ii += 1 + continue + + + " expression of selection, field access, array access + elseif items[ii] =~ s:RE_SELECT_OR_ACCESS + let subs = split(substitute(items[ii], s:RE_SELECT_OR_ACCESS, '\1;\2', ''), ';') + let ident = subs[0] + let brackets = get(subs, 1, '') + + " package members + if itemkind/10 == 2 && empty(brackets) && !s:IsKeyword(ident) + let qn = join(items[:ii], '.') + if type(ti) == type([]) + let idx = s:Index(ti, ident, 'word') + if idx >= 0 + if ti[idx].kind == 'P' + unlet ti + let ti = s:GetMembers(qn) + let ii += 1 + continue + elseif ti[idx].kind == 'C' + unlet ti + let ti = s:DoGetClassInfo(qn) + let itemkind = 11 + let ii += 1 + continue + endif + endif + endif + + + " type members + elseif itemkind/10 == 1 && empty(brackets) + if ident ==# 'class' || ident ==# 'this' || ident ==# 'super' + let ti = s:DoGetClassInfo(ident == 'class' ? 'java.lang.Class' : join(items[:ii-1], '.')) + let itemkind = ident ==# 'this' ? 1 : ident ==# 'super' ? 2 : 0 + let ii += 1 + continue + + elseif !s:IsKeyword(ident) && type(ti) == type({}) && get(ti, 'tag', '') == 'CLASSDEF' + " accessible static field + "let idx = s:Index(get(ti, 'fields', []), ident, 'n') + "if idx >= 0 && s:IsStatic(ti.fields[idx].m) + " let ti = s:ArrayAccess(ti.fields[idx].t, items[ii]) + let members = s:SearchMember(ti, ident, 1, itemkind, 1, 0) + if !empty(members[2]) + let ti = s:ArrayAccess(members[2][0].t, items[ii]) + let itemkind = 0 + let ii += 1 + continue + endif + + " accessible nested type + "if !empty(filter(copy(get(ti, 'classes', [])), 'strpart(v:val, strridx(v:val, ".")) ==# "' . ident . '"')) + if !empty(members[0]) + let ti = s:DoGetClassInfo(join(items[:ii], '.')) + let ii += 1 + continue + endif + endif + + + " instance members + elseif itemkind/10 == 0 && !s:IsKeyword(ident) + if type(ti) == type({}) && get(ti, 'tag', '') == 'CLASSDEF' + "let idx = s:Index(get(ti, 'fields', []), ident, 'n') + "if idx >= 0 + " let ti = s:ArrayAccess(ti.fields[idx].t, items[ii]) + let members = s:SearchMember(ti, ident, 1, itemkind, 1, 0) + let itemkind = 0 + if !empty(members[2]) + let ti = s:ArrayAccess(members[2][0].t, items[ii]) + let ii += 1 + continue + endif + endif + endif + endif + + return [] + endwhile + + + " type info or package info --> members + if !empty(ti) + if type(ti) == type({}) + if get(ti, 'tag', '') == 'CLASSDEF' + if get(ti, 'name', '') == '!' + return [{'kind': 'f', 'word': 'class', 'menu': 'Class'}] + elseif get(ti, 'name', '') == '[' + return s:ARRAY_TYPE_MEMBERS + elseif itemkind < 20 + return s:DoGetMemberList(ti, itemkind) + endif + elseif get(ti, 'tag', '') == 'PACKAGE' + " TODO: ti -> members, in addition to packages in dirs + return s:GetMembers( substitute(join(items, '.'), '\s', '', 'g') ) + endif + elseif type(ti) == type([]) + return ti + endif + endif + + return [] +endfunction + + +fu! s:MethodInvocation(expr, ti, itemkind) + let subs = split(substitute(a:expr, '\s*\(' . s:RE_IDENTIFIER . '\)\s*\((.*\)', '\1;\2', ''), ';') + + " all methods matched + if empty(a:ti) + let methods = s:SearchForName(subs[0], 0, 1)[1] + elseif type(a:ti) == type({}) && get(a:ti, 'tag', '') == 'CLASSDEF' + let methods = s:SearchMember(a:ti, subs[0], 1, a:itemkind, 1, 0, a:itemkind == 2)[1] +" let methods = s:filter(get(a:ti, 'methods', []), 'item.n == "' . subs[0] . '"') +" if a:itemkind == 1 || a:itemkind == 2 +" let methods += s:filter(get(a:ti, 'declared_methods', []), 'item.n == "' . subs[0] . '"') +" endif + else + let methods = [] + endif + + let method = s:DetermineMethod(methods, subs[1]) + if !empty(method) + return s:ArrayAccess(method.r, a:expr) + endif + return {} +endfu + +fu! s:ArrayAccess(arraytype, expr) + if a:expr =~ s:RE_BRACKETS | return {} | endif + let typename = a:arraytype + + let dims = 0 + if typename[0] == '[' || typename[-1:] == ']' || a:expr[-1:] == ']' + let dims = s:CountDims(a:expr) - s:CountDims(typename) + if dims == 0 + let typename = matchstr(typename, s:RE_IDENTIFIER) + elseif dims < 0 + return s:ARRAY_TYPE_INFO + else + "echoerr 'dims exceeds' + endif + endif + if dims == 0 + if typename != 'void' && !s:IsBuiltinType(typename) + return s:DoGetClassInfo(typename) + endif + endif + return {} +endfu + + +" Quick information {{{1 +function! MyBalloonExpr() + if (searchdecl(v:beval_text, 1, 0) == 0) + return s:GetVariableDeclaration() + endif + return '' +" return 'Cursor is at line ' . v:beval_lnum . +" \', column ' . v:beval_col . +" \ ' of file ' . bufname(v:beval_bufnr) . +" \ ' on word "' . v:beval_text . '"' +endfunction +"set bexpr=MyBalloonExpr() +"set ballooneval + +" parameters information {{{1 +fu! javacomplete#CompleteParamsInfo(findstart, base) + if a:findstart + return col('.') - 1 + endif + + + let mi = s:GetMethodInvocationExpr(s:GetStatement()) + if empty(mi.method) + return [] + endif + + " TODO: how to determine overloaded functions + "let mi.params = s:EvalParams(mi.params) + if empty(mi.expr) + let methods = s:SearchForName(mi.method, 0, 1)[1] + let result = eval('[' . s:DoGetMethodList(methods) . ']') + elseif mi.method == '+' + let result = s:GetConstructorList(mi.expr) + else + let result = s:CompleteAfterDot(mi.expr) + endif + + if !empty(result) + if !empty(mi.method) && mi.method != '+' + let result = filter(result, "type(v:val) == type('') ? v:val ==# '" . mi.method . "' : v:val['word'] ==# '" . mi.method . "('") + endif + return result + endif +endfu + +" scanning and parsing {{{1 + +" Search back from the cursor position till meeting '{' or ';'. +" '{' means statement start, ';' means end of a previous statement. +" Return: statement before cursor +" Note: It's the base for parsing. And It's OK for most cases. +function! s:GetStatement() + if getline('.') =~ '^\s*\(import\|package\)\s\+' + return strpart(getline('.'), match(getline('.'), '\(import\|package\)'), col('.')-1) + endif + + let lnum_old = line('.') + let col_old = col('.') + + while 1 + if search('[{};]\|<%\|<%!', 'bW') == 0 + let lnum = 1 + let col = 1 + else + if s:InCommentOrLiteral(line('.'), col('.')) + continue + endif + + normal w + let lnum = line('.') + let col = col('.') + endif + break + endwhile + + silent call cursor(lnum_old, col_old) + return s:MergeLines(lnum, col, lnum_old, col_old) +endfunction + +fu! s:MergeLines(lnum, col, lnum_old, col_old) + let lnum = a:lnum + let col = a:col + + let str = '' + if lnum < a:lnum_old + let str = s:Prune(strpart(getline(lnum), a:col-1)) + let lnum += 1 + while lnum < a:lnum_old + let str .= s:Prune(getline(lnum)) + let lnum += 1 + endwhile + let col = 1 + endif + let lastline = strpart(getline(a:lnum_old), col-1, a:col_old-col) + let str .= s:Prune(lastline, col) + let str = s:RemoveBlockComments(str) + " generic in JAVA 5+ + while match(str, s:RE_TYPE_ARGUMENTS) != -1 + let str = substitute(str, '\(' . s:RE_TYPE_ARGUMENTS . '\)', '\=repeat(" ", len(submatch(1)))', 'g') + endwhile + let str = substitute(str, '\s\s\+', ' ', 'g') + let str = substitute(str, '\([.()]\)[ \t]\+', '\1', 'g') + let str = substitute(str, '[ \t]\+\([.()]\)', '\1', 'g') + return s:Trim(str) . matchstr(lastline, '\s*$') +endfu + +" Extract a clean expr, removing some non-necessary characters. +fu! s:ExtractCleanExpr(expr) + let cmd = substitute(a:expr, '[ \t\r\n]\+\([.()[\]]\)', '\1', 'g') + let cmd = substitute(cmd, '\([.()[\]]\)[ \t\r\n]\+', '\1', 'g') + + let pos = strlen(cmd)-1 + while pos >= 0 && cmd[pos] =~ '[a-zA-Z0-9_.)\]]' + if cmd[pos] == ')' + let pos = s:SearchPairBackward(cmd, pos, '(', ')') + elseif cmd[pos] == ']' + let pos = s:SearchPairBackward(cmd, pos, '[', ']') + endif + let pos -= 1 + endwhile + + " try looking back for "new" + let idx = match(strpart(cmd, 0, pos+1), '\<new[ \t\r\n]*$') + + return strpart(cmd, idx != -1 ? idx : pos+1) +endfu + +fu! s:ParseExpr(expr) + let items = [] + let s = 0 + " recognize ClassInstanceCreationExpr as a whole + let e = matchend(a:expr, '^\s*new\s\+' . s:RE_QUALID . '\s*[([]')-1 + if e < 0 + let e = match(a:expr, '[.([]') + endif + let isparen = 0 + while e >= 0 + if a:expr[e] == '.' + let subexpr = strpart(a:expr, s, e-s) + call extend(items, isparen ? s:ProcessParentheses(subexpr) : [subexpr]) + let isparen = 0 + let s = e + 1 + elseif a:expr[e] == '(' + let e = s:GetMatchedIndexEx(a:expr, e, '(', ')') + let isparen = 1 + if e < 0 + break + else + let e = matchend(a:expr, '^\s*[.[]', e+1)-1 + continue + endif + elseif a:expr[e] == '[' + let e = s:GetMatchedIndexEx(a:expr, e, '[', ']') + if e < 0 + break + else + let e = matchend(a:expr, '^\s*[.[]', e+1)-1 + continue + endif + endif + let e = match(a:expr, '[.([]', s) + endwhile + let tail = strpart(a:expr, s) + if tail !~ '^\s*$' + call extend(items, isparen ? s:ProcessParentheses(tail) : [tail]) + endif + + return items +endfu + +" Given optional argument, call s:ParseExpr() to parser the nonparentheses expr +fu! s:ProcessParentheses(expr, ...) + let s = matchend(a:expr, '^\s*(') + if s != -1 + let e = s:GetMatchedIndexEx(a:expr, s-1, '(', ')') + if e >= 0 + let tail = strpart(a:expr, e+1) + if tail =~ '^\s*[\=$' + return s:ProcessParentheses(strpart(a:expr, s, e-s), 1) + elseif tail =~ '^\s*\w' + return [strpart(a:expr, 0, e+1) . 'obj.'] + endif + endif + + " multi-dot-expr except for new expr + elseif a:0 > 0 && stridx(a:expr, '.') != match(a:expr, '\.\s*$') && a:expr !~ '^\s*new\s\+' + return s:ParseExpr(a:expr) + endif + return [a:expr] +endfu + +" return {'expr': , 'method': , 'params': } +fu! s:GetMethodInvocationExpr(expr) + let idx = strlen(a:expr)-1 + while idx >= 0 + if a:expr[idx] == '(' + break + elseif a:expr[idx] == ')' + let idx = s:SearchPairBackward(a:expr, idx, '(', ')') + elseif a:expr[idx] == ']' + let idx = s:SearchPairBackward(a:expr, idx, '[', ']') + endif + let idx -= 1 + endwhile + + let mi = {'expr': strpart(a:expr, 0, idx+1), 'method': '', 'params': strpart(a:expr, idx+1)} + let idx = match(mi.expr, '\<new\s\+' . s:RE_QUALID . '\s*(\s*$') + if idx >= 0 + let mi.method = '+' + let mi.expr = substitute(matchstr(strpart(mi.expr, idx+4), s:RE_QUALID), '\s', '', 'g') + else + let idx = match(mi.expr, '\<' . s:RE_IDENTIFIER . '\s*(\s*$') + if idx >= 0 + let subs = s:SplitAt(mi.expr, idx-1) + let mi.method = substitute(subs[1], '\s*(\s*$', '', '') + let mi.expr = s:ExtractCleanExpr(subs[0]) + endif + endif + return mi +endfu + +" imports {{{1 +function! s:GenerateImports() + let imports = [] + + let lnum_old = line('.') + let col_old = col('.') + call cursor(1, 1) + + if &ft == 'jsp' + while 1 + let lnum = search('\<import\s*=[''"]', 'W') + if (lnum == 0) + break + endif + + let str = getline(lnum) + if str =~ '<%\s*@\s*page\>' || str =~ '<jsp:\s*directive.page\>' + let str = substitute(str, '.*import=[''"]\([a-zA-Z0-9_$.*, \t]\+\)[''"].*', '\1', '') + for item in split(str, ',') + call add(imports, substitute(item, '\s', '', 'g')) + endfor + endif + endwhile + else + while 1 + let lnum = search('\<import\>', 'W') + if (lnum == 0) + break + elseif !s:InComment(line("."), col(".")-1) + normal w + " TODO: search semicolon or import keyword, excluding comment + let stat = matchstr(getline(lnum)[col('.')-1:], '\(static\s\+\)\?\(' .s:RE_QUALID. '\%(\s*\.\s*\*\)\?\)\s*;') + if !empty(stat) + call add(imports, stat[:-2]) + endif + endif + endwhile + endif + + call cursor(lnum_old, col_old) + return imports +endfunction + +fu! s:GetImports(kind, ...) + let filekey = a:0 > 0 && !empty(a:1) ? a:1 : s:GetCurrentFileKey() + let props = get(s:files, filekey, {}) + if !has_key(props, a:kind) + let props['imports'] = filekey == s:GetCurrentFileKey() ? s:GenerateImports() : props.unit.imports + let props['imports_static'] = [] + let props['imports_fqn'] = [] + let props['imports_star'] = ['java.lang.'] + if &ft == 'jsp' || filekey =~ '\.jsp$' + let props.imports_star += ['javax.servlet.', 'javax.servlet.http.', 'javax.servlet.jsp.'] + endif + + for import in props.imports + let subs = split(substitute(import, '^\s*\(static\s\+\)\?\(' .s:RE_QUALID. '\%(\s*\.\s*\*\)\?\)\s*$', '\1;\2', ''), ';', 1) + let qid = substitute(subs[1] , '\s', '', 'g') + if !empty(subs[0]) + call add(props.imports_static, qid) + elseif qid[-1:] == '*' + call add(props.imports_star, qid[:-2]) + else + call add(props.imports_fqn, qid) + endif + endfor + let s:files[filekey] = props + endif + return get(props, a:kind, []) +endfu + +" search for name in +" return the fqn matched +fu! s:SearchSingleTypeImport(name, fqns) + let matches = s:filter(a:fqns, 'item =~# ''\<' . a:name . '$''') + if len(matches) == 1 + return matches[0] + elseif !empty(matches) + echoerr 'Name "' . a:name . '" conflicts between ' . join(matches, ' and ') + return matches[0] + endif + return '' +endfu + +" search for name in static imports, return list of members with the same name +" return [types, methods, fields] +fu! s:SearchStaticImports(name, fullmatch) + let result = [[], [], []] + let candidates = [] " list of the canonical name + for item in s:GetImports('imports_static') + if item[-1:] == '*' " static import on demand + call add(candidates, item[:-3]) + elseif item[strridx(item, '.')+1:] ==# a:name + \ || (!a:fullmatch && item[strridx(item, '.')+1:] =~ '^' . a:name) + call add(candidates, item[:strridx(item, '.')]) + endif + endfor + if empty(candidates) + return result + endif + + + " read type info which are not in cache + let commalist = '' + for typename in candidates + if !has_key(s:cache, typename) + let commalist .= typename . ',' + endif + endfor + if commalist != '' + let res = s:RunReflection('-E', commalist, 's:SearchStaticImports in Batch') + if res =~ "^{'" + let dict = eval(res) + for key in keys(dict) + let s:cache[key] = s:Sort(dict[key]) + endfor + endif + endif + + " search in all candidates + for typename in candidates + let ti = get(s:cache, typename, 0) + if type(ti) == type({}) && get(ti, 'tag', '') == 'CLASSDEF' + let members = s:SearchMember(ti, a:name, a:fullmatch, 12, 1, 0) + let result[1] += members[1] + let result[2] += members[2] + "let pattern = 'item.n ' . (a:fullmatch ? '==# ''' : '=~# ''^') . a:name . ''' && s:IsStatic(item.m)' + "let result[1] += s:filter(get(ti, 'methods', []), pattern) + "let result[2] += s:filter(get(ti, 'fields', []), pattern) + else + " TODO: mark the wrong import declaration. + endif + endfor + return result +endfu + + +" search decl {{{1 +" Return: The declaration of identifier under the cursor +" Note: The type of a variable must be imported or a fqn. +function! s:GetVariableDeclaration() + let lnum_old = line('.') + let col_old = col('.') + + silent call search('[^a-zA-Z0-9$_.,?<>[\] \t\r\n]', 'bW') " call search('[{};(,]', 'b') + normal w + let lnum = line('.') + let col = col('.') + if (lnum == lnum_old && col == col_old) + return '' + endif + +" silent call search('[;){]') +" let lnum_end = line('.') +" let col_end = col('.') +" let declaration = '' +" while (lnum <= lnum_end) +" let declaration = declaration . getline(lnum) +" let lnum = lnum + 1 +" endwhile +" let declaration = strpart(declaration, col-1) +" let declaration = substitute(declaration, '\.[ \t]\+', '.', 'g') + + silent call cursor(lnum_old, col_old) + return s:MergeLines(lnum, col, lnum_old, col_old) +endfunction + +function! s:FoundClassDeclaration(type) + let lnum_old = line('.') + let col_old = col('.') + call cursor(1, 1) + while 1 + let lnum = search('\<\C\(class\|interface\|enum\)[ \t\n\r]\+' . a:type . '[< \t\n\r]', 'W') + if lnum == 0 || !s:InCommentOrLiteral(line('.'), col('.')) + break + endif + endwhile + + " search mainly for the cases: " class /* block comment */ Ident" + " " class // comment \n Ident " + if lnum == 0 + let found = 0 + while !found + let lnum = search('\<\C\(class\|interface\|enum\)[ \t\n\r]\+', 'W') + if lnum == 0 + break + elseif s:InCommentOrLiteral(line('.'), col('.')) + continue + else + normal w + " skip empty line + while getline(line('.'))[col('.')-1] == '' + normal w + endwhile + let lnum = line('.') + let col = col('.') + while 1 + if match(getline(lnum)[col-1:], '^[ \t\n\r]*' . a:type . '[< \t\n\r]') >= 0 + let found = 1 + " meets comment + elseif match(getline(lnum)[col-1:], '^[ \t\n\r]*\(//\|/\*\)') >= 0 + if getline(lnum)[col-1:col] == '//' + normal $eb + else + let lnum = search('\*\/', 'W') + if lnum == 0 + break + endif + normal web + endif + let lnum = line('.') + let col = col('.') + continue + endif + break + endwhile + endif + endwhile + endif + + silent call cursor(lnum_old, col_old) + return lnum +endfu + +fu! s:FoundClassLocally(type) + " current path + if globpath(expand('%:p:h'), a:type . '.java') != '' + return 1 + endif + + " + let srcpath = javacomplete#GetSourcePath(1) + let file = globpath(srcpath, substitute(fqn, '\.', '/', 'g') . '.java') + if file != '' + return 1 + endif + + return 0 +endfu + +" regexp samples: +" echo search('\(\(public\|protected|private\)[ \t\n\r]\+\)\?\(\(static\)[ \t\n\r]\+\)\?\(\<class\>\|\<interface\>\)[ \t\n\r]\+HelloWorld[^a-zA-Z0-9_$]', 'W') +" echo substitute(getline('.'), '.*\(\(public\|protected\|private\)[ \t\n\r]\+\)\?\(\(static\)[ \t\n\r]\+\)\?\(\<class\>\|\<interface\>\)\s\+\([a-zA-Z0-9_]\+\)\s\+\(\(implements\|extends\)\s\+\([^{]\+\)\)\?\s*{.*', '["\1", "\2", "\3", "\4", "\5", "\6", "\8", "\9"]', '') +" code sample: +function! s:GetClassDeclarationOf(type) + call cursor(1, 1) + let decl = [] + + let lnum = search('\(\<class\>\|\<interface\>\)[ \t\n\r]\+' . a:type . '[^a-zA-Z0-9_$]', 'W') + if (lnum != 0) + " TODO: search back for optional 'public | private' and 'static' + " join lines till to '{' + let lnum_end = search('{') + if (lnum_end != 0) + let str = '' + while (lnum <= lnum_end) + let str = str . getline(lnum) + let lnum = lnum + 1 + endwhile + + exe "let decl = " . substitute(str, '.*\(\<class\>\|\<interface\>\)\s\+\([a-zA-Z0-9_]\+\)\s\+\(\(implements\|extends\)\s\+\([^{]\+\)\)\?\s*{.*', '["\1", "\2", "\4", "\5"]', '') + endif + endif + + return decl +endfunction + +" return list +" 0 class | interface +" 1 name +" [2 implements | extends ] +" [3 parent list ] +function! s:GetThisClassDeclaration() + let lnum_old = line('.') + let col_old = col('.') + + while (1) + call search('\(\<class\C\>\|\<interface\C\>\|\<enum\C\>\)[ \t\r\n]\+', 'bW') + if !s:InComment(line("."), col(".")-1) + if getline('.')[col('.')-2] !~ '\S' + break + endif + end + endwhile + + " join lines till to '{' + let str = '' + let lnum = line('.') + call search('{') + let lnum_end = line('.') + while (lnum <= lnum_end) + let str = str . getline(lnum) + let lnum = lnum + 1 + endwhile + + + let declaration = substitute(str, '.*\(\<class\>\|\<interface\>\)\s\+\([a-zA-Z0-9_]\+\)\(\s\+\(implements\|extends\)\s\+\([^{]\+\)\)\?\s*{.*', '["\1", "\2", "\4", "\5"]', '') + call cursor(lnum_old, col_old) + if declaration !~ '^[' + echoerr 'Some error occurs when recognizing this class:' . declaration + return ['', ''] + endif + exe "let list = " . declaration + return list +endfunction + +" searches for name of a var or a field and determines the meaning {{{1 + +" The standard search order of a variable or field is as follows: +" 1. Local variables declared in the code block, for loop, or catch clause +" from current scope up to the most outer block, a method or an initialization block +" 2. Parameters if the code is in a method or ctor +" 3. Fields of the type +" 4. Accessible inherited fields. +" 5. If the type is a nested type, +" local variables of the enclosing block or fields of the enclosing class. +" Note that if the type is a static nested type, only static members of an enclosing block or class are searched +" Reapply this rule to the upper block and class enclosing the enclosing type recursively +" 6. Accessible static fields imported. +" It is allowed that several fields with the same name. + +" The standard search order of a method is as follows: +" 1. Methods of the type +" 2. Accessible inherited methods. +" 3. Methods of the enclosing class if the type is a nested type. +" 4. Accessible static methods imported. +" It is allowed that several methods with the same name and signature. + +" first return at once if found one. +" fullmatch 1 - equal, 0 - match beginning +" return [types, methods, fields, vars] +fu! s:SearchForName(name, first, fullmatch) + let result = [[], [], [], []] + if s:IsKeyword(a:name) + return result + endif + + " use java_parser.vim + if javacomplete#GetSearchdeclMethod() == 4 + " declared in current file + let unit = javacomplete#parse() + let targetPos = java_parser#MakePos(line('.')-1, col('.')-1) + let trees = s:SearchNameInAST(unit, a:name, targetPos, a:fullmatch) + for tree in trees + if tree.tag == 'VARDEF' + call add(result[2], tree) + elseif tree.tag == 'METHODDEF' + call add(result[1], tree) + elseif tree.tag == 'CLASSDEF' + call add(result[0], tree.name) + endif + endfor + + if a:first && result != [[], [], [], []] | return result | endif + + " Accessible inherited members + let type = get(s:SearchTypeAt(unit, targetPos), -1, {}) + if !empty(type) + let members = s:SearchMember(type, a:name, a:fullmatch, 2, 1, 0, 1) + let result[0] += members[0] + let result[1] += members[1] + let result[2] += members[2] +" "let ti = s:AddInheritedClassInfo({}, type) +" if !empty(ti) +" let comparator = a:fullmatch ? "=~# '^" : "==# '" +" let result[0] += s:filter(get(ti, 'classes', []), 'item ' . comparator . a:name . "'") +" let result[1] += s:filter(get(ti, 'methods', []), 'item.n ' . comparator . a:name . "'") +" let result[2] += s:filter(get(ti, 'fields', []), 'item.n ' . comparator . a:name . "'") +" if a:0 > 0 +" let result[1] += s:filter(get(ti, 'declared_methods', []), 'item.n ' . comparator . a:name . "'") +" let result[2] += s:filter(get(ti, 'declared_fields', []), 'item.n ' . comparator . a:name . "'") +" endif +" if a:first && result != [[], [], [], []] | return result | endif +" endif + endif + + " static import + let si = s:SearchStaticImports(a:name, a:fullmatch) + let result[1] += si[1] + let result[2] += si[2] + endif + return result +endfu + +" TODO: how to determine overloaded functions +fu! s:DetermineMethod(methods, parameters) + return get(a:methods, 0, {}) +endfu + +" Parser.GetType() in insenvim +function! s:GetDeclaredClassName(var) + let var = s:Trim(a:var) + call s:Trace('GetDeclaredClassName for "' . var . '"') + if var =~# '^\(this\|super\)$' + return var + endif + + + " Special handling for builtin objects in JSP + if &ft == 'jsp' + if get(s:JSP_BUILTIN_OBJECTS, a:var, '') != '' + return s:JSP_BUILTIN_OBJECTS[a:var] + endif + endif + + " use java_parser.vim + if javacomplete#GetSearchdeclMethod() == 4 + let variable = get(s:SearchForName(var, 1, 1)[2], -1, {}) + return get(variable, 'tag', '') == 'VARDEF' ? java_parser#type2Str(variable.vartype) : get(variable, 't', '') + endif + + + let ic = &ignorecase + setlocal noignorecase + + let searched = javacomplete#GetSearchdeclMethod() == 2 ? s:Searchdecl(var, 1, 0) : searchdecl(var, 1, 0) + if (searched == 0) + " code sample: + " String tmp; java. + " lang. String str, value; + " for (int i = 0, j = 0; i < 10; i++) { + " j = 0; + " } + let declaration = s:GetVariableDeclaration() + " Assume it a class member, and remove modifiers + let class = substitute(declaration, '^\(public\s\+\|protected\s\+\|private\s\+\|abstract\s\+\|static\s\+\|final\s\+\|native\s\+\)*', '', '') + let class = substitute(class, '\s*\([a-zA-Z0-9_.]\+\)\(\[\]\)\?\s\+.*', '\1\2', '') + let class = substitute(class, '\([a-zA-Z0-9_.]\)<.*', '\1', '') + call s:Info('class: "' . class . '" declaration: "' . declaration . '" for ' . a:var) + let &ignorecase = ic + if class != '' && class !=# a:var && class !=# 'import' && class !=# 'class' + return class + endif + endif + + let &ignorecase = ic + call s:Trace('GetDeclaredClassName: cannot find') + return '' +endfunction + +" using java_parser.vim {{{1 +" javacomplete#parse() {{{2 +fu! javacomplete#parse(...) + let filename = a:0 == 0 ? '%' : a:1 + + let changed = 0 + if filename == '%' + let filename = s:GetCurrentFileKey() + let props = get(s:files, filename, {}) + if get(props, 'changedtick', -1) != b:changedtick + let changed = 1 + let props.changedtick = b:changedtick + let lines = getline('^', '$') + endif + else + let props = get(s:files, filename, {}) + if get(props, 'modifiedtime', 0) != getftime(filename) + let changed = 1 + let props.modifiedtime = getftime(filename) + let lines = readfile(filename) + endif + endif + + if changed + call java_parser#InitParser(lines) + call java_parser#SetLogLevel(5) + let props.unit = java_parser#compilationUnit() + + let package = has_key(props.unit, 'package') ? props.unit.package . '.' : '' + call s:UpdateFQN(props.unit, package) + endif + let s:files[filename] = props + return props.unit +endfu + +" update fqn for toplevel types or nested types. +" not for local type or anonymous type +fu! s:UpdateFQN(tree, qn) + if a:tree.tag == 'TOPLEVEL' + for def in a:tree.types + call s:UpdateFQN(def, a:qn) + endfor + elseif a:tree.tag == 'CLASSDEF' + let a:tree.fqn = a:qn . a:tree.name + for def in a:tree.defs + if def.tag == 'CLASSDEF' + call s:UpdateFQN(def, a:tree.fqn . '.') + endif + endfor + endif +endfu + +" TreeVisitor {{{2 +fu! s:visitTree(tree, param) dict + if type(a:tree) == type({}) + exe get(self, get(a:tree, 'tag', ''), '') + elseif type(a:tree) == type([]) + for tree in a:tree + call self.visit(tree, a:param) + endfor + endif +endfu + +let s:TreeVisitor = {'visit': function('s:visitTree'), + \ 'TOPLEVEL' : 'call self.visit(a:tree.types, a:param)', + \ 'BLOCK' : 'let stats = a:tree.stats | if stats == [] | call java_parser#GotoPosition(a:tree.pos) | let stats = java_parser#block().stats | endif | call self.visit(stats, a:param)', + \ 'DOLOOP' : 'call self.visit(a:tree.body, a:param) | call self.visit(a:tree.cond, a:param)', + \ 'WHILELOOP' : 'call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.body, a:param)', + \ 'FORLOOP' : 'call self.visit(a:tree.init, a:param) | call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.step, a:param) | call self.visit(a:tree.body, a:param)', + \ 'FOREACHLOOP' : 'call self.visit(a:tree.var, a:param) | call self.visit(a:tree.expr, a:param) | call self.visit(a:tree.body, a:param)', + \ 'LABELLED' : 'call self.visit(a:tree.body, a:param)', + \ 'SWITCH' : 'call self.visit(a:tree.selector, a:param) | call self.visit(a:tree.cases, a:param)', + \ 'CASE' : 'call self.visit(a:tree.pat, a:param) | call self.visit(a:tree.stats, a:param)', + \ 'SYNCHRONIZED': 'call self.visit(a:tree.lock, a:param) | call self.visit(a:tree.body, a:param)', + \ 'TRY' : 'call self.visit(a:tree.body, a:param) | call self.visit(a:tree.catchers, a:param) | call self.visit(a:tree.finalizer, a:param) ', + \ 'CATCH' : 'call self.visit(a:tree.param,a:param) | call self.visit(a:tree.body, a:param)', + \ 'CONDEXPR' : 'call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.truepart, a:param) | call self.visit(a:tree.falsepart, a:param)', + \ 'IF' : 'call self.visit(a:tree.cond, a:param) | call self.visit(a:tree.thenpart, a:param) | if has_key(a:tree, "elsepart") | call self.visit(a:tree.elsepart, a:param) | endif', + \ 'EXEC' : 'call self.visit(a:tree.expr, a:param)', + \ 'APPLY' : 'call self.visit(a:tree.meth, a:param) | call self.visit(a:tree.args, a:param)', + \ 'NEWCLASS' : 'call self.visit(a:tree.def, a:param)' + \} + +let s:TV_CMP_POS = 'a:tree.pos <= a:param.pos && a:param.pos <= get(a:tree, "endpos", -1)' +let s:TV_CMP_POS_BODY = 'has_key(a:tree, "body") && a:tree.body.pos <= a:param.pos && a:param.pos <= get(a:tree.body, "endpos", -1)' + +" Return a stack of enclosing types (including local or anonymous classes). +" Given the optional argument, return all (toplevel or static member) types besides enclosing types. +fu! s:SearchTypeAt(tree, targetPos, ...) + let s:TreeVisitor.CLASSDEF = 'if a:param.allNonLocal || ' . s:TV_CMP_POS . ' | call add(a:param.result, a:tree) | call self.visit(a:tree.defs, a:param) | endif' + let s:TreeVisitor.METHODDEF = 'if ' . s:TV_CMP_POS_BODY . ' | call self.visit(a:tree.body, a:param) | endif' + let s:TreeVisitor.VARDEF = 'if has_key(a:tree, "init") && !a:param.allNonLocal && ' . s:TV_CMP_POS . ' | call self.visit(a:tree.init, a:param) | endif' + + let result = [] + call s:TreeVisitor.visit(a:tree, {'result': result, 'pos': a:targetPos, 'allNonLocal': a:0 == 0 ? 0 : 1}) + return result +endfu + +" a:1 match beginning +" return a stack of matching name +fu! s:SearchNameInAST(tree, name, targetPos, fullmatch) + let comparator = a:fullmatch ? '==#' : '=~# "^" .' + let cmd = 'if a:tree.name ' .comparator. ' a:param.name | call add(a:param.result, a:tree) | endif' + let s:TreeVisitor.CLASSDEF = 'if ' . s:TV_CMP_POS . ' | ' . cmd . ' | call self.visit(a:tree.defs, a:param) | endif' + let s:TreeVisitor.METHODDEF = cmd . ' | if ' . s:TV_CMP_POS_BODY . ' | call self.visit(a:tree.params, a:param) | call self.visit(a:tree.body, a:param) | endif' + let s:TreeVisitor.VARDEF = cmd . ' | if has_key(a:tree, "init") && ' . s:TV_CMP_POS . ' | call self.visit(a:tree.init, a:param) | endif' + + let result = [] + call s:TreeVisitor.visit(a:tree, {'result': result, 'pos': a:targetPos, 'name': a:name}) + "call s:Info(a:name . ' ' . string(result) . ' line: ' . line('.') . ' col: ' . col('.')) . ' ' . a:targetPos + return result +endfu + + +" javacomplete#Searchdecl {{{2 +" TODO: +fu! javacomplete#Searchdecl() + let var = expand('<cword>') + + let line = line('.')-1 + let col = col('.')-1 + + + if var =~# '^\(this\|super\)$' + if &ft == 'jsp' + return '' + endif + + let matchs = s:SearchTypeAt(javacomplete#parse(), java_parser#MakePos(line, col)) + + let stat = s:GetStatement() + for t in matchs + if stat =~ t.name + let coor = java_parser#DecodePos(t.pos) + return var . '(' . (coor.line+1) . ',' . (coor.col) . ') ' . getline(coor.line+1) + endif + endfor + if len(matchs) > 0 + let coor = java_parser#DecodePos(matchs[len(matchs)-1].pos) + return var . '(' . (coor.line+1) . ',' . (coor.col) . ') ' . getline(coor.line+1) + endif + return '' + endif + + " Type.this. + " new Type() + " new Type(param1, param2) + " this.field + " super.field + + let s:log = [] + + + " It may be an imported class. + let imports = [] + for fqn in s:GetImports('imports_fqn') + if fqn =~# '\<' . var . '\>$' + call add(imports, fqn) + endif + endfor + if len(imports) > 1 + echoerr 'Imports conflicts between ' . join(imports, ' and ') + endif + + + " Search in this buffer + let matchs = s:SearchNameInAST(javacomplete#parse(), var, java_parser#MakePos(line, col), 1) + + + let hint = var . ' ' + if !empty(matchs) + let tree = matchs[len(matchs)-1] + let coor = java_parser#DecodePos(tree.pos) + let hint .= '(' . (coor.line+1) . ',' . (coor.col) . ') ' + let hint .= getline(coor.line+1) "string(tree) + else + for fqn in imports + let ci = s:DoGetClassInfo(fqn) + if !empty(ci) + let hint .= ' ' . fqn + endif + " TODO: get javadoc + endfor + + endif + return hint +endfu + + +" java {{{1 + +fu! s:IsBuiltinType(name) + return index(s:PRIMITIVE_TYPES, a:name) >= 0 +endfu + +fu! s:IsKeyword(name) + return index(s:KEYWORDS, a:name) >= 0 +endfu + +fu! s:HasKeyword(name) + return a:name =~# s:RE_KEYWORDS +endfu + +fu! s:TailOfQN(qn) + return a:qn[strridx(a:qn, '.')+1:] +endfu + +" options {{{1 +" Methods to search declaration {{{2 +" 1 - by builtin searchdecl() +" 2 - by special Searchdecl() +" 4 - by java_parser +fu! javacomplete#GetSearchdeclMethod() + if &ft == 'jsp' + return 1 + endif + return exists('s:searchdecl') ? s:searchdecl : 4 +endfu + +fu! javacomplete#SetSearchdeclMethod(method) + let s:searchdecl = a:method +endfu + +" JDK1.1 {{{2 +fu! javacomplete#UseJDK11() + let s:isjdk11 = 1 +endfu + +" java compiler {{{2 +fu! javacomplete#GetCompiler() + return exists('s:compiler') && s:compiler !~ '^\s*$' ? s:compiler : 'javac' +endfu + +fu! javacomplete#SetCompiler(compiler) + let s:compiler = a:compiler +endfu + +" jvm launcher {{{2 +fu! javacomplete#GetJVMLauncher() + return exists('s:interpreter') && s:interpreter !~ '^\s*$' ? s:interpreter : 'java' +endfu + +fu! javacomplete#SetJVMLauncher(interpreter) + if javacomplete#GetJVMLauncher() != a:interpreter + let s:cache = {} + endif + let s:interpreter = a:interpreter +endfu + +" sourcepath {{{2 +fu! javacomplete#AddSourcePath(s) + if !isdirectory(a:s) + echoerr 'invalid source path: ' . a:s + return + endif + let path = fnamemodify(a:s, ':p:h') + if !exists('s:sourcepath') + let s:sourcepath = [path] + elseif index(s:sourcepath, path) == -1 + call add(s:sourcepath, path) + endif +endfu + +fu! javacomplete#DelSourcePath(s) + if !exists('s:sourcepath') || !isdirectory(a:s)| return | endif + let idx = index(s:sourcepath, a:s) + if idx != -1 + call remove(s:sourcepath, idx) + endif +endfu + +fu! javacomplete#SetSourcePath(s) + let paths = type(a:s) == type([]) ? a:s : split(a:s, javacomplete#GetClassPathSep()) + let s:sourcepath = [] + for path in paths + if isdirectory(path) + call add(s:sourcepath, fnamemodify(path, ':p:h')) + endif + endfor +endfu + +" return the sourcepath. Given argument, add current path or default package root path +" NOTE: Avoid path duplicate, otherwise globpath() will return duplicate result. +fu! javacomplete#GetSourcePath(...) + return join(s:GetSourceDirs(a:0 > 0 && a:1 ? expand('%:p') : ''), s:PATH_SEP) +endfu + +fu! s:GetSourceDirs(filepath, ...) + let dirs = exists('s:sourcepath') ? s:sourcepath : [] + + if !empty(a:filepath) + let filepath = fnamemodify(a:filepath, ':p:h') + + " get source path according to file path and package name + let packageName = a:0 > 0 ? a:1 : s:GetPackageName() + if packageName != '' + let path = fnamemodify(substitute(filepath, packageName, '', 'g'), ':p:h') + if index(dirs, path) < 0 + call add(dirs, path) + endif + endif + + " Consider current path as a sourcepath + if index(dirs, filepath) < 0 + call add(dirs, filepath) + endif + endif + return dirs +endfu + +" classpath {{{2 +fu! javacomplete#AddClassPath(s) + if !isdirectory(a:s) + echoerr 'invalid classpath: ' . a:s + return + endif + + if !exists('s:classpath') + let s:classpath = [a:s] + elseif index(s:classpath, a:s) == -1 + call add(s:classpath, a:s) + endif + let s:cache = {} +endfu + +fu! javacomplete#DelClassPath(s) + if !exists('s:classpath') | return | endif + let idx = index(s:classpath, a:s) + if idx != -1 + call remove(s:classpath, idx) + endif +endfu + +fu! javacomplete#SetClassPath(s) + if type(a:s) == type("") + let s:classpath = split(a:s, javacomplete#GetClassPathSep()) + elseif type(a:s) == type([]) + let s:classpath = a:s + endif + let s:cache = {} +endfu + +fu! javacomplete#GetClassPathSep() + return s:PATH_SEP +endfu + +fu! javacomplete#GetClassPath() + return exists('s:classpath') ? join(s:classpath, javacomplete#GetClassPathSep()) : '' +endfu + +" s:GetClassPath() {{{2 +fu! s:GetClassPath() + let path = s:GetJavaCompleteClassPath() . javacomplete#GetClassPathSep() + + if &ft == 'jsp' + let path .= s:GetClassPathOfJsp() + endif + + if exists('b:classpath') && b:classpath !~ '^\s*$' + return path . b:classpath + endif + + if exists('s:classpath') + return path . javacomplete#GetClassPath() + endif + + if exists('g:java_classpath') && g:java_classpath !~ '^\s*$' + return path . g:java_classpath + endif + + return path . $CLASSPATH +endfu + +fu! s:GetJavaCompleteClassPath() + " remove *.class from wildignore if it exists, so that globpath doesn't ignore Reflection.class + " vim versions >= 702 can add the 1 flag to globpath which ignores '*.class" in wildingore + let has_class = 0 + if &wildignore =~# "*.class" + set wildignore-=*.class + let has_class = 1 + endif + + let classfile = globpath(&rtp, 'autoload/Reflection.class') + if classfile == '' + let classfile = globpath($HOME, 'Reflection.class') + endif + if classfile == '' + " try to find source file and compile to $HOME + let srcfile = globpath(&rtp, 'autoload/Reflection.java') + if srcfile != '' + exe '!' . javacomplete#GetCompiler() . ' -d "' . $CLASS_DEST . '" "' . srcfile . '"' + let classfile = globpath($HOME, 'Reflection.class') + if classfile == '' + echo srcfile . ' can not be compiled. Please check it' + endif + else + echo 'No Reflection.class found in $HOME or any autoload directory of the &rtp. And no Reflection.java found in any autoload directory of the &rtp to compile.' + endif + endif + + " add *.class to wildignore if it existed before + if has_class == 1 + set wildignore+=*.class + endif + + return fnamemodify(classfile, ':p:h') +endfu + +fu! s:GetClassPathOfJsp() + if exists('b:classpath_jsp') + return b:classpath_jsp + endif + + let b:classpath_jsp = '' + let path = expand('%:p:h') + while 1 + if isdirectory(path . '/WEB-INF' ) + if isdirectory(path . '/WEB-INF/classes') + let b:classpath_jsp .= s:PATH_SEP . path . '/WEB-INF/classes' + endif + if isdirectory(path . '/WEB-INF/lib') + let libs = globpath(path . '/WEB-INF/lib', '*.jar') + if libs != '' + let b:classpath_jsp .= s:PATH_SEP . substitute(libs, "\n", s:PATH_SEP, 'g') + endif + endif + return b:classpath_jsp + endif + + let prev = path + let path = fnamemodify(path, ":p:h:h") + if path == prev + break + endif + endwhile + return '' +endfu + +" return only classpath which are directories +fu! s:GetClassDirs() + let dirs = [] + for path in split(s:GetClassPath(), s:PATH_SEP) + if isdirectory(path) + call add(dirs, fnamemodify(path, ':p:h')) + endif + endfor + return dirs +endfu + +" s:GetPackageName() {{{2 +fu! s:GetPackageName() + let lnum_old = line('.') + let col_old = col('.') + + call cursor(1, 1) + let lnum = search('^\s*package[ \t\r\n]\+\([a-zA-Z][a-zA-Z0-9.]*\);', 'w') + let packageName = substitute(getline(lnum), '^\s*package\s\+\([a-zA-Z][a-zA-Z0-9.]*\);', '\1', '') + + call cursor(lnum_old, col_old) + return packageName +endfu + +fu! s:IsStatic(modifier) + return a:modifier[strlen(a:modifier)-4] +endfu + +" utilities {{{1 +" Convert a file name into the unique form. +" Similar with fnamemodify(). NOTE that ':gs' should not be used. +fu! s:fnamecanonize(fname, mods) + return fnamemodify(a:fname, a:mods . ':gs?[\\/]\+?/?') +endfu + +" Similar with filter(), but returns a new list instead of operating in-place. +" `item` has the value of the current item. +fu! s:filter(expr, string) + if type(a:expr) == type([]) + let result = [] + for item in a:expr + if eval(a:string) + call add(result, item) + endif + endfor + return result + else + let result = {} + for item in items(a:expr) + if eval(a:string) + let result[item[0]] = item[1] + endif + endfor + return result + endif +endfu + +fu! s:Index(list, expr, key) + let i = 0 + while i < len(a:list) + if get(a:list[i], a:key, '') == a:expr + return i + endif + let i += 1 + endwhile + return -1 +endfu + +fu! s:Match(list, expr, key) + let i = 0 + while i < len(a:list) + if get(a:list[i], a:key, '') =~ a:expr + return i + endif + let i += 1 + endwhile + return -1 +endfu + +fu! s:KeepCursor(cmd) + let lnum_old = line('.') + let col_old = col('.') + exe a:cmd + call cursor(lnum_old, col_old) +endfu + +fu! s:InCommentOrLiteral(line, col) + if has("syntax") && &ft != 'jsp' + return synIDattr(synID(a:line, a:col, 1), "name") =~? '\(Comment\|String\|Character\)' + endif +endfu + +function! s:InComment(line, col) + if has("syntax") && &ft != 'jsp' + return synIDattr(synID(a:line, a:col, 1), "name") =~? 'comment' + endif +" if getline(a:line) =~ '\s*\*' +" return 1 +" endif +" let idx = strridx(getline(a:line), '//') +" if idx >= 0 && idx < a:col +" return 1 +" endif +" return 0 +endfunction + +" set string literal empty, remove comments, trim begining or ending spaces +" test case: ' sb. /* block comment*/ append( "stringliteral" ) // comment ' +function! s:Prune(str, ...) + if a:str =~ '^\s*$' | return '' | endif + + let str = substitute(a:str, '"\(\\\(["\\''ntbrf]\)\|[^"]\)*"', '""', 'g') + let str = substitute(str, '\/\/.*', '', 'g') + let str = s:RemoveBlockComments(str) + return a:0 > 0 ? str : str . ' ' +endfunction + +" Given argument, replace block comments with spaces of same number +fu! s:RemoveBlockComments(str, ...) + let result = a:str + let ib = match(result, '\/\*') + let ie = match(result, '\*\/') + while ib != -1 && ie != -1 && ib < ie + let result = strpart(result, 0, ib) . (a:0 == 0 ? ' ' : repeat(' ', ie-ib+2)) . result[ie+2: ] + let ib = match(result, '\/\*') + let ie = match(result, '\*\/') + endwhile + return result +endfu + +fu! s:Trim(str) + let str = substitute(a:str, '^\s*', '', '') + return substitute(str, '\s*$', '', '') +endfu + +fu! s:SplitAt(str, index) + return [strpart(a:str, 0, a:index+1), strpart(a:str, a:index+1)] +endfu + +" TODO: search pair used in string, like +" 'create(ao.fox("("), new String).foo().' +function! s:GetMatchedIndexEx(str, idx, one, another) + let pos = a:idx + while 0 <= pos && pos < len(a:str) + let pos = match(a:str, '['. a:one . escape(a:another, ']') .']', pos+1) + if pos != -1 + if a:str[pos] == a:one + let pos = s:GetMatchedIndexEx(a:str, pos, a:one, a:another) + elseif a:str[pos] == a:another + break + endif + endif + endwhile + return 0 <= pos && pos < len(a:str) ? pos : -3 +endfunction + +function! s:SearchPairBackward(str, idx, one, another) + let idx = a:idx + let n = 0 + while idx >= 0 + let idx -= 1 + if a:str[idx] == a:one + if n == 0 + break + endif + let n -= 1 + elseif a:str[idx] == a:another " nested + let n += 1 + endif + endwhile + return idx +endfunction + +fu! s:CountDims(str) + if match(a:str, '[[\]]') == -1 + return 0 + endif + + " int[] -> [I, String[] -> + let dims = len(matchstr(a:str, '^[\+')) + if dims == 0 + let idx = len(a:str)-1 + while idx >= 0 && a:str[idx] == ']' + let dims += 1 + let idx = s:SearchPairBackward(a:str, idx, '[', ']')-1 + endwhile + endif + return dims +endfu + +fu! s:GotoUpperBracket() + let searched = 0 + while (!searched) + call search('[{}]', 'bW') + if getline('.')[col('.')-1] == '}' + normal % + else + let searched = 1 + endif + endwhile +endfu + +" Improve recognition of variable declaration using my version of searchdecl() for accuracy reason. +" TODO: +fu! s:Searchdecl(name, ...) + let global = a:0 > 0 ? a:1 : 0 + let thisblock = a:0 > 1 ? a:2 : 1 + + call search('\<' . a:name . '\>', 'bW') + let lnum_old = line('.') + let col_old = col('.') + + call s:GotoUpperBracket() + let lnum_bracket = line('.') + let col_bracket = col('.') + call search('\<' . a:name . '\>', 'W', lnum_old) + if line('.') != lnum_old || col('.') != col_old + return 0 + endif + + " search globally + if global + call cursor(lnum_bracket, col_bracket) + " search backward + while (1) + if search('\([{}]\|\<' . a:name . '\>\)', 'bW') == 0 + break + endif + if s:InComment(line('.'), col('.')) "|| s:InStringLiteral() + continue + endif + let cword = expand('<cword>') + if cword == a:name + return 0 + endif + if getline('.')[col('.')-1] == '}' + normal % + endif + endwhile + + call cursor(lnum_old, col_old) + " search forward + call search('[{};]', 'W') + while (1) + if search('\([{}]\|\<' . a:name . '\>\)', 'W') == 0 + break + endif + if s:InComment(line('.'), col('.')) "|| s:InStringLiteral() + continue + endif + let cword = expand('<cword>') + if cword == a:name + return 0 + endif + if getline('.')[col('.')-1] == '{' + normal % + endif + endwhile + endif + return 1 +endfu +"nmap <F8> :call <SID>Searchdecl(expand('<cword>'))<CR> + +fu! javacomplete#Exe(cmd) + exe a:cmd +endfu + +" cache utilities {{{1 + +" key of s:files for current buffer. It may be the full path of current file or the bufnr of unnamed buffer, and is updated when BufEnter, BufLeave. +fu! s:GetCurrentFileKey() + return has("autocmd") ? s:curfilekey : empty(expand('%')) ? bufnr('%') : expand('%:p') +endfu + +fu! s:SetCurrentFileKey() + let s:curfilekey = empty(expand('%')) ? bufnr('%') : expand('%:p') +endfu + +call s:SetCurrentFileKey() +if has("autocmd") + autocmd BufEnter *.java call s:SetCurrentFileKey() + autocmd FileType java call s:SetCurrentFileKey() +endif + + +" Log utilities {{{1 +fu! s:WatchVariant(variant) + "echoerr a:variant +endfu + +" level +" 5 off/fatal +" 4 error +" 3 warn +" 2 info +" 1 debug +" 0 trace +fu! javacomplete#SetLogLevel(level) + let s:loglevel = a:level +endfu + +fu! javacomplete#GetLogLevel() + return exists('s:loglevel') ? s:loglevel : 3 +endfu + +fu! javacomplete#GetLogContent() + return s:log +endfu + +fu! s:Trace(msg) + call s:Log(0, a:msg) +endfu + +fu! s:Debug(msg) + call s:Log(1, a:msg) +endfu + +fu! s:Info(msg) + call s:Log(2, a:msg) +endfu + +fu! s:Log(level, key, ...) + if a:level >= javacomplete#GetLogLevel() + echo a:key + call add(s:log, a:key) + endif +endfu + +fu! s:System(cmd, caller) + call s:WatchVariant(a:cmd) + let t = reltime() + let res = system(a:cmd) + call s:Debug(reltimestr(reltime(t)) . 's to exec "' . a:cmd . '" by ' . a:caller) + return res +endfu + +" functions to get information {{{1 +" utilities {{{2 +fu! s:MemberCompare(m1, m2) + return a:m1['n'] == a:m2['n'] ? 0 : a:m1['n'] > a:m2['n'] ? 1 : -1 +endfu + +fu! s:Sort(ci) + let ci = a:ci + if has_key(ci, 'fields') + call sort(ci['fields'], 's:MemberCompare') + endif + if has_key(ci, 'methods') + call sort(ci['methods'], 's:MemberCompare') + endif + return ci +endfu + +" Function to run Reflection {{{2 +fu! s:RunReflection(option, args, log) + let classpath = '' + if !exists('s:isjdk11') + let classpath = ' -classpath "' . s:GetClassPath() . '" ' + endif + + let cmd = javacomplete#GetJVMLauncher() . classpath . ' Reflection ' . a:option . ' "' . a:args . '"' + return s:System(cmd, a:log) +endfu +" class information {{{2 + + +" The standard search order of a FQN is as follows: +" 1. a file-name toplevel type or static member type accessed by the file-name type declared in source files +" 2. other types declared in source files +" 3. an accessible loadable type. +" parameters: +" fqns - list of fqn +" srcpaths - a comma-separated list of directory names. +" a:1 - search all. +" return a dict of fqn -> type info +" precondition: +" NOTE: call expand() to convert path to standard form +fu! s:DoGetTypeInfoForFQN(fqns, srcpath, ...) + if empty(a:fqns) || empty(a:srcpath) + return + endif + + " 1 + let files = {} " fqn -> java file path + for fqn in a:fqns + " toplevel type + let filepath = globpath(a:srcpath, substitute(fqn, '\.', '/', 'g') . '.java') + if filepath != '' + let files[fqn] = expand(filepath) + + " nested type + elseif stridx(fqn, '.') >= 0 + let idents = split(fqn, '\.') + let i = len(idents)-2 + while i >= 0 + let filepath = globpath(a:srcpath, join(idents[:i], '/') . '.java') + if filepath != '' + let files[fqn] = expand(filepath) + break + endif + let i -= 1 + endwhile + endif + endfor + + + " 2 + let dirs = {} " dir.idents -> names of nested type + " dir.qfitems -> items of quick fix + " dir.fqn -> fqn + for fqn in a:fqns + if !has_key(files, fqn) + for path in split(a:srcpath, ',') + let idents = split(fqn, '\.') + let i = len(idents)-2 + while i >= 0 + let dirpath = path . '/' . join(idents[:i], '/') + " it is a package + if isdirectory(dirpath) + let dirs[fnamemodify(dirpath, ':p:h:gs?[\\/]\+?/?')] = {'fqn': fqn, 'idents': idents[i+1:]} + break + endif + let i -= 1 + endwhile + endfor + endif + endfor + + if !empty(dirs) + let items = {} " dir -> items of quick fix + + let filepatterns = '' + for dirpath in keys(dirs) + let filepatterns .= escape(dirpath, ' \') . '/*.java ' + endfor + + let cwd = fnamemodify(expand('%:p:h'), ':p:h:gs?[\\/]\+?/?') + exe 'vimgrep /\s*' . s:RE_TYPE_DECL . '/jg ' . filepatterns + for item in getqflist() + if item.text !~ '^\s*\*\s\+' + let text = matchstr(s:Prune(item.text, -1), '\s*' . s:RE_TYPE_DECL) + if text != '' + let subs = split(substitute(text, '\s*' . s:RE_TYPE_DECL, '\1;\2;\3', ''), ';', 1) + let dirpath = fnamemodify(bufname(item.bufnr), ':p:h:gs?[\\/]\+?/?') + let idents = dirs[dirpath].idents + if index(idents, subs[2]) >= 0 && (subs[0] =~ '\C\<public\>' || dirpath == cwd) " FIXME? + let item.subs = subs + let dirs[dirpath].qfitems = get(dirs[dirpath], 'qfitems', []) + [item] + endif + endif + endif + endfor + + for dirpath in keys(dirs) + " a. names of nested type must be existed in the same file + " PackageName.NonFileNameTypeName.NestedType.NestedNestedType + let qfitems = get(dirs[dirpath], 'qfitems', []) + let nr = 0 + for ident in dirs[dirpath].idents + for item in qfitems + if item.subs[2] == ident + let nr += 1 + endif + endfor + endfor + if nr == len(dirs[dirpath].idents) + " b. TODO: Check whether one enclosed another is correct + let files[fqn] = expand(bufname(qfitems[0].bufnr)) + endif + endfor + endif + + + call s:Info('FQN1&2: ' . string(keys(files))) + for fqn in keys(files) + if !has_key(s:cache, fqn) || get(get(s:files, files[fqn], {}), 'modifiedtime', 0) != getftime(files[fqn]) + let ti = s:GetClassInfoFromSource(fqn[strridx(fqn, '.')+1:], files[fqn]) + if !empty(ti) + let s:cache[fqn] = s:Sort(ti) + endif + endif + if (a:0 == 0 || !a:1) + return + endif + endfor + + + " 3 + let commalist = '' + for fqn in a:fqns + if has_key(s:cache, fqn) && (a:0 == 0 || !a:1) + return + else "if stridx(fqn, '.') >= 0 + let commalist .= fqn . ',' + endif + endfor + if !empty(commalist) + let res = s:RunReflection('-E', commalist, 'DoGetTypeInfoForFQN in Batch') + if res =~ "^{'" + let dict = eval(res) + for key in keys(dict) + if !has_key(s:cache, key) + if type(dict[key]) == type({}) + let s:cache[key] = s:Sort(dict[key]) + elseif type(dict[key]) == type([]) + let s:cache[key] = sort(dict[key]) + endif + endif + endfor + endif + endif +endfu + +" a:1 filepath +" a:2 package name +fu! s:DoGetClassInfo(class, ...) + if has_key(s:cache, a:class) + return s:cache[a:class] + endif + + " array type: TypeName[] or '[I' or '[[Ljava.lang.String;' + if a:class[-1:] == ']' || a:class[0] == '[' + return s:ARRAY_TYPE_INFO + endif + + " either this or super is not qualified + if a:class == 'this' || a:class == 'super' + if &ft == 'jsp' + let ci = s:DoGetReflectionClassInfo('javax.servlet.jsp.HttpJspPage') + if a:class == 'this' + " TODO: search methods defined in <%! [declarations] %> + " search methods defined in other jsp files included + " avoid including self directly or indirectly + endif + return ci + endif + + call s:Info('A0. ' . a:class) + " this can be a local class or anonymous class as well as static type + let t = get(s:SearchTypeAt(javacomplete#parse(), java_parser#MakePos(line('.')-1, col('.')-1)), -1, {}) + if !empty(t) + " What will be returned for super? + " - the protected or public inherited fields and methods. No ctors. + " - the (public static) fields of interfaces. + " - the methods of the Object class. + " What will be returned for this? + " - besides the above, all fields and methods of current class. No ctors. + return s:Sort(s:Tree2ClassInfo(t)) + "return s:Sort(s:AddInheritedClassInfo(a:class == 'this' ? s:Tree2ClassInfo(t) : {}, t, 1)) + endif + + return {} + endif + + + if a:class !~ '^\s*' . s:RE_QUALID . '\s*$' || s:HasKeyword(a:class) + return {} + endif + + + let typename = substitute(a:class, '\s', '', 'g') + let filekey = a:0 > 0 ? a:1 : s:GetCurrentFileKey() + let packagename = a:0 > 1 ? a:2 : s:GetPackageName() + let srcpath = join(s:GetSourceDirs(a:0 > 0 && a:1 != bufnr('%') ? a:1 : expand('%:p'), packagename), ',') + + let names = split(typename, '\.') + " remove the package name if in the same packge + if len(names) > 1 + if packagename == join(names[:-2], '.') + let names = names[-1:] + endif + endif + + " a FQN + if len(names) > 1 + call s:DoGetTypeInfoForFQN([typename], srcpath) + let ci = get(s:cache, typename, {}) + if get(ci, 'tag', '') == 'CLASSDEF' + return s:cache[typename] + elseif get(ci, 'tag', '') == 'PACKAGE' + return {} + endif + endif + + + " The standard search order of a simple type name is as follows: + " 1. The current type including inherited types. + " 2. A nested type of the current type. + " 3. Explicitly named imported types (single type import). + " 4. Other types declared in the same package. Not only current directory. + " 5. Implicitly named imported types (import on demand). + + " 1 & 2. + " NOTE: inherited types are treated as normal + if filekey == s:GetCurrentFileKey() + let simplename = typename[strridx(typename, '.')+1:] + if s:FoundClassDeclaration(simplename) != 0 + call s:Info('A1&2') + let ci = s:GetClassInfoFromSource(simplename, '%') + " do not cache it + if !empty(ci) + return ci + endif + endif + else + let ci = s:GetClassInfoFromSource(typename, filekey) + if !empty(ci) + return ci + endif + endif + + " 3. + " NOTE: PackageName.Ident, TypeName.Ident + let fqn = s:SearchSingleTypeImport(typename, s:GetImports('imports_fqn', filekey)) + if !empty(fqn) + call s:Info('A3') + call s:DoGetTypeInfoForFQN([fqn], srcpath) + let ti = get(s:cache, fqn, {}) + if get(ti, 'tag', '') != 'CLASSDEF' + " TODO: mark the wrong import declaration. + endif + return ti + endif + + " 4 & 5 + " NOTE: Keeps the fqn of the same package first!! + call s:Info('A4&5') + let fqns = [empty(packagename) ? typename : packagename . '.' . typename] + for p in s:GetImports('imports_star', filekey) + call add(fqns, p . typename) + endfor + call s:DoGetTypeInfoForFQN(fqns, srcpath) + for fqn in fqns + if has_key(s:cache, fqn) + return get(s:cache[fqn], 'tag', '') == 'CLASSDEF' ? s:cache[fqn] : {} + endif + endfor + + return {} +endfu + +" Rules of overriding and hiding: +" 1. Fields cannot be overridden; they can only be hidden. +" In the subclass, the hidden field of superclass can no longer be accessed +" directly by its simple name. `super` or another reference must be used. +" 2. A method can be overriden only if it is accessible. +" When overriding methods, both the signature and return type must be the +" same as in the superclass. +" 3. Static members cannot be overridden; they can only be hidden +" -- whether a field or a method. But hiding static members has little effect, +" because static should be accessed via the name of its declaring class. +" Given optional argument, add protected, default (package) access, private members. +"fu! s:MergeClassInfo(ci, another, ...) +" if empty(a:another) | return a:ci | endif +" +" if empty(a:ci) +" let ci = copy(a:another) +"" if a:0 > 0 && a:1 +"" call extend(ci.fields, get(a:another, 'declared_fields', [])) +"" call extend(ci.methods, get(a:another, 'declared_methods', [])) +"" endif +" return ci +" endif +" +" call extend(a:ci.methods, a:another.methods) +" +" for f in a:another.fields +" if s:Index(a:ci.fields, f.n, 'n') < 0 +" call add(a:ci.fields, f) +" endif +" endfor +" return a:ci +"endfu + + +" Parameters: +" class the qualified class name +" Return: TClassInfo or {} when not found +" See ClassInfoFactory.getClassInfo() in insenvim. +function! s:DoGetReflectionClassInfo(fqn) + if !has_key(s:cache, a:fqn) + let res = s:RunReflection('-C', a:fqn, 's:DoGetReflectionClassInfo') + if res =~ '^{' + let s:cache[a:fqn] = s:Sort(eval(res)) + elseif res =~ '^[' + for type in eval(res) + if get(type, 'name', '') != '' + let s:cache[type.name] = s:Sort(type) + endif + endfor + else + let b:errormsg = res + endif + endif + return get(s:cache, a:fqn, {}) +endfunction + +fu! s:GetClassInfoFromSource(class, filename) + let ci = {} + if len(tagfiles()) > 0 + let ci = s:DoGetClassInfoFromTags(a:class) + endif + + if empty(ci) + call s:Info('Use java_parser.vim to generate class information') + let unit = javacomplete#parse(a:filename) + let targetPos = a:filename == '%' ? java_parser#MakePos(line('.')-1, col('.')-1) : -1 + for t in s:SearchTypeAt(unit, targetPos, 1) + if t.name == a:class + let t.filepath = a:filename == '%' ? s:GetCurrentFileKey() : expand(a:filename) + return s:Tree2ClassInfo(t) + "return s:AddInheritedClassInfo(s:Tree2ClassInfo(t), t) + endif + endfor + endif + return ci +endfu + +fu! s:Tree2ClassInfo(t) + let t = a:t + + " fill fields and methods + let t.fields = [] + let t.methods = [] + let t.ctors = [] + let t.classes = [] + for def in t.defs + if def.tag == 'METHODDEF' + call add(def.n == t.name ? t.ctors : t.methods, def) + elseif def.tag == 'VARDEF' + call add(t.fields, def) + elseif def.tag == 'CLASSDEF' + call add(t.classes, t.fqn . '.' . def.name) + endif + endfor + + " convert type name in extends to fqn for class defined in source files + if !has_key(a:t, 'classpath') && has_key(a:t, 'extends') + if has_key(a:t, 'filepath') && a:t.filepath != s:GetCurrentFileKey() + let filepath = a:t.filepath + let packagename = get(s:files[filepath].unit, 'package', '') + else + let filepath = expand('%:p') + let packagename = s:GetPackageName() + endif + + let extends = a:t.extends + let i = 0 + while i < len(extends) + let ci = s:DoGetClassInfo(java_parser#type2Str(extends[i]), filepath, packagename) + if has_key(ci, 'fqn') + let extends[i] = ci.fqn + endif + let i += 1 + endwhile + endif + + return t +endfu + +"fu! s:AddInheritedClassInfo(ci, t, ...) +" let ci = a:ci +" " add inherited fields and methods +" let list = [] +" for i in get(a:t, 'extends', []) +" call add(list, java_parser#type2Str(i)) +" endfor +" +" if has_key(a:t, 'filepath') && a:t.filepath != expand('%:p') +" let filepath = a:t.filepath +" let props = get(s:files, a:t.filepath, {}) +" let packagename = get(props.unit, 'package', '') +" else +" let filepath = expand('%:p') +" let packagename = s:GetPackageName() +" endif +" +" for id in list +" let ci = s:MergeClassInfo(ci, s:DoGetClassInfo(id, filepath, packagename), a:0 > 0 && a:1) +" endfor +" return ci +"endfu + +" To obtain information of the class in current file or current folder, or +" even in current project. +function! s:DoGetClassInfoFromTags(class) + " find tag of a:class declaration + let tags = taglist('^' . a:class) + let filename = '' + let cmd = '' + for tag in tags + if has_key(tag, 'kind') + if tag['kind'] == 'c' + let filename = tag['filename'] + let cmd = tag['cmd'] + break + endif + endif + endfor + + let tags = taglist('^' . (empty(b:incomplete) ? '.*' : b:incomplete) ) + if filename != '' + call filter(tags, "v:val['filename'] == '" . filename . "' && has_key(v:val, 'class') ? v:val['class'] == '" . a:class . "' : 1") + endif + + let ci = {'name': a:class} + " extends and implements + let ci['ctors'] = [] + let ci['fields'] = [] + let ci['methods'] = [] + + " members + for tag in tags + let member = {'n': tag['name']} + + " determine kind + let kind = 'm' + if has_key(tag, 'kind') + let kind = tag['kind'] + endif + + let cmd = tag['cmd'] + if cmd =~ '\<static\>' + let member['m'] = '1000' + else + let member['m'] = '' + endif + + let desc = substitute(cmd, '/^\s*', '', '') + let desc = substitute(desc, '\s*{\?\s*$/$', '', '') + + if kind == 'm' + " description + if cmd =~ '\<static\>' + let desc = substitute(desc, '\s\+static\s\+', ' ', '') + endif + let member['d'] = desc + + let member['p'] = '' + let member['r'] = '' + if tag['name'] == a:class + call add(ci['ctors'], member) + else + call add(ci['methods'], member) + endif + elseif kind == 'f' + let member['t'] = substitute(desc, '\([a-zA-Z0-9_[\]]\)\s\+\<' . tag['name'] . '\>.*$', '\1', '') + call add(ci['fields'], member) + endif + endfor + return ci +endfu + +" package information {{{2 + +fu! s:DoGetInfoByReflection(class, option) + if has_key(s:cache, a:class) + return s:cache[a:class] + endif + + let res = s:RunReflection(a:option, a:class, 's:DoGetInfoByReflection') + if res =~ '^[{\[]' + let v = eval(res) + if type(v) == type([]) + let s:cache[a:class] = sort(v) + elseif type(v) == type({}) + if get(v, 'tag', '') =~# '^\(PACKAGE\|CLASSDEF\)$' + let s:cache[a:class] = v + else + call extend(s:cache, v, 'force') + endif + endif + unlet v + else + let b:errormsg = res + endif + + return get(s:cache, a:class, {}) +endfu + +" search in members {{{2 +" TODO: what about default access? +" public for all +" protected for this or super +" private for this +fu! s:CanAccess(mods, kind) + return (a:mods[-4:-4] || a:kind/10 == 0) + \ && (a:kind == 1 || a:mods[-1:] + \ || (a:mods[-3:-3] && (a:kind == 1 || a:kind == 2)) + \ || (a:mods[-2:-2] && a:kind == 1)) +endfu + +fu! s:SearchMember(ci, name, fullmatch, kind, returnAll, memberkind, ...) + let result = [[], [], []] + + if a:kind != 13 + for m in (a:0 > 0 && a:1 ? [] : get(a:ci, 'fields', [])) + ((a:kind == 1 || a:kind == 2) ? get(a:ci, 'declared_fields', []) : []) + if empty(a:name) || (a:fullmatch ? m.n ==# a:name : m.n =~# '^' . a:name) + if s:CanAccess(m.m, a:kind) + call add(result[2], m) + endif + endif + endfor + + for m in (a:0 > 0 && a:1 ? [] : get(a:ci, 'methods', [])) + ((a:kind == 1 || a:kind == 2) ? get(a:ci, 'declared_methods', []) : []) + if empty(a:name) || (a:fullmatch ? m.n ==# a:name : m.n =~# '^' . a:name) + if s:CanAccess(m.m, a:kind) + call add(result[1], m) + endif + endif + endfor + endif + + if a:kind/10 != 0 + let types = get(a:ci, 'classes', []) + for t in types + if empty(a:name) || (a:fullmatch ? t[strridx(t, '.')+1:] ==# a:name : t[strridx(t, '.')+1:] =~# '^' . a:name) + if !has_key(s:cache, t) || !has_key(s:cache[t], 'flags') || a:kind == 1 || s:cache[t].flags[-1:] + call add(result[0], t) + endif + endif + endfor + endif + + " key `classpath` indicates it is a loaded class from classpath + " All public members of a loaded class are stored in current ci + if !has_key(a:ci, 'classpath') || (a:kind == 1 || a:kind == 2) + for i in get(a:ci, 'extends', []) + let ci = s:DoGetClassInfo(java_parser#type2Str(i)) + let members = s:SearchMember(ci, a:name, a:fullmatch, a:kind == 1 ? 2 : a:kind, a:returnAll, a:memberkind) + let result[0] += members[0] + let result[1] += members[1] + let result[2] += members[2] + endfor + endif + return result +endfu + + +" generate member list {{{2 + +fu! s:DoGetFieldList(fields) + let s = '' + for field in a:fields + let s .= "{'kind':'" . (s:IsStatic(field.m) ? "F" : "f") . "','word':'" . field.n . "','menu':'" . field.t . "','dup':1}," + endfor + return s +endfu + +fu! s:DoGetMethodList(methods, ...) + let paren = a:0 == 0 || !a:1 ? '(' : '' + let s = '' + for method in a:methods + let s .= "{'kind':'" . (s:IsStatic(method.m) ? "M" : "m") . "','word':'" . method.n . paren . "','abbr':'" . method.n . "()','menu':'" . method.d . "','dup':'1'}," + endfor + return s +endfu + +" kind: +" 0 - for instance, 1 - this, 2 - super, 3 - class, 4 - array, 5 - method result, 6 - primitive type +" 11 - for type, with `class` and static member and nested types. +" 12 - for import static, no lparen for static methods +" 13 - for import or extends or implements, only nested types +" 20 - for package +fu! s:DoGetMemberList(ci, kind) + if type(a:ci) != type({}) || a:ci == {} + return [] + endif + + let s = a:kind == 11 ? "{'kind': 'C', 'word': 'class', 'menu': 'Class'}," : '' + + let members = s:SearchMember(a:ci, '', 1, a:kind, 1, 0, a:kind == 2) + + " add accessible member types + if a:kind / 10 != 0 + " Use dup here for member type can share name with field. + for class in members[0] + "for class in get(a:ci, 'classes', []) + let v = get(s:cache, class, {}) + if v == {} || v.flags[-1:] + let s .= "{'kind': 'C', 'word': '" . substitute(class, a:ci.name . '\.', '\1', '') . "','dup':1}," + endif + endfor + endif + + if a:kind != 13 + let fieldlist = [] + let sfieldlist = [] + for field in members[2] + "for field in get(a:ci, 'fields', []) + if s:IsStatic(field['m']) + call add(sfieldlist, field) + elseif a:kind / 10 == 0 + call add(fieldlist, field) + endif + endfor + + let methodlist = [] + let smethodlist = [] + for method in members[1] + if s:IsStatic(method['m']) + call add(smethodlist, method) + elseif a:kind / 10 == 0 + call add(methodlist, method) + endif + endfor + + if a:kind / 10 == 0 + let s .= s:DoGetFieldList(fieldlist) + let s .= s:DoGetMethodList(methodlist) + endif + let s .= s:DoGetFieldList(sfieldlist) + let s .= s:DoGetMethodList(smethodlist, a:kind == 12) + + let s = substitute(s, '\<' . a:ci.name . '\.', '', 'g') + let s = substitute(s, '\<java\.lang\.', '', 'g') + let s = substitute(s, '\<\(public\|static\|synchronized\|transient\|volatile\|final\|strictfp\|serializable\|native\)\s\+', '', 'g') + endif + return eval('[' . s . ']') +endfu + +" interface {{{2 + +function! s:GetMemberList(class) + if s:IsBuiltinType(a:class) + return [] + endif + + return s:DoGetMemberList(s:DoGetClassInfo(a:class), 0) +endfunction + +fu! s:GetStaticMemberList(class) + return s:DoGetMemberList(s:DoGetClassInfo(a:class), 11) +endfu + +function! s:GetConstructorList(class) + let ci = s:DoGetClassInfo(a:class) + if empty(ci) + return [] + endif + + let s = '' + for ctor in get(ci, 'ctors', []) + let s .= "{'kind': '+', 'word':'". a:class . "(','abbr':'" . ctor.d . "','dup':1}," + endfor + + let s = substitute(s, '\<java\.lang\.', '', 'g') + let s = substitute(s, '\<public\s\+', '', 'g') + return eval('[' . s . ']') +endfunction + +" Name can be a (simple or qualified) package name, or a (simple or qualified) +" type name. +fu! s:GetMembers(fqn, ...) + let list = [] + let isClass = 0 + + let v = s:DoGetInfoByReflection(a:fqn, '-E') + if type(v) == type([]) + let list = v + elseif type(v) == type({}) && v != {} + if get(v, 'tag', '') == 'PACKAGE' + if b:context_type == s:CONTEXT_IMPORT_STATIC || b:context_type == s:CONTEXT_IMPORT + call add(list, {'kind': 'P', 'word': '*;'}) + endif + if b:context_type != s:CONTEXT_PACKAGE_DECL + for c in sort(get(v, 'classes', [])) + call add(list, {'kind': 'C', 'word': c}) + endfor + endif + for p in sort(get(v, 'subpackages', [])) + call add(list, {'kind': 'P', 'word': p}) + endfor + else " elseif get(v, 'tag', '') == 'CLASSDEF' + let isClass = 1 + let list += s:DoGetMemberList(v, b:context_type == s:CONTEXT_IMPORT || b:context_type == s:CONTEXT_NEED_TYPE ? 13 : b:context_type == s:CONTEXT_IMPORT_STATIC ? 12 : 11) + endif + endif + + if !isClass + let list += s:DoGetPackageInfoInDirs(a:fqn, b:context_type == s:CONTEXT_PACKAGE_DECL) + endif + + return list +endfu + +" a:1 incomplete mode +" return packages in classes directories or source pathes +fu! s:DoGetPackageInfoInDirs(package, onlyPackages, ...) + let list = [] + + let pathes = s:GetSourceDirs(expand('%:p')) + for path in s:GetClassDirs() + if index(pathes, path) <= 0 + call add(pathes, path) + endif + endfor + + let globpattern = a:0 > 0 ? a:package . '*' : substitute(a:package, '\.', '/', 'g') . '/*' + let matchpattern = a:0 > 0 ? a:package : a:package . '[\\/]' + for f in split(globpath(join(pathes, ','), globpattern), "\n") + for path in pathes + let idx = matchend(f, escape(path, ' \') . '[\\/]\?\C' . matchpattern) + if idx != -1 + let name = (a:0 > 0 ? a:package : '') . strpart(f, idx) + if f[-5:] == '.java' + if !a:onlyPackages + call add(list, {'kind': 'C', 'word': name[:-6]}) + endif + elseif name =~ '^' . s:RE_IDENTIFIER . '$' && isdirectory(f) && f !~# 'CVS$' + call add(list, {'kind': 'P', 'word': name}) + endif + endif + endfor + endfor + return list +endfu +" }}} +"}}} +" vim:set fdm=marker sw=2 nowrap: |