" Vim completion script for java " Maintainer: artur shaik " " This file contains everything related to collecting source data function! s:Log(log) let log = type(a:log) == type("") ? a:log : string(a:log) call javacomplete#logger#Log("[collector] ". log) endfunction " a:1 - filepath " a:2 - package name function! javacomplete#collector#DoGetClassInfo(class, ...) let class = type(a:class) == type({}) ? a:class.name : a:class call s:Log("get class info. class: ". class) if class != 'this' && class != 'super' && has_key(g:JavaComplete_Cache, class) call s:Log("class info from cache") return g:JavaComplete_Cache[class] endif " array type: TypeName[] or '[I' or '[[Ljava.lang.String;' if class[-1:] == ']' || class[0] == '[' return g:J_ARRAY_TYPE_INFO endif let filekey = a:0 > 0 && len(a:1) > 0 ? a:1 : javacomplete#GetCurrentFileKey() let packagename = a:0 > 1 && len(a:2) > 0 ? a:2 : javacomplete#collector#GetPackageName() let unit = javacomplete#parseradapter#Parse(filekey) let pos = java_parser#MakePos(line('.') - 1, col('.') - 1) let t = get(javacomplete#parseradapter#SearchTypeAt(unit, pos), -1, {}) if has_key(t, 'extends') if type(t.extends) == type([]) && len(t.extends) > 0 if type(t.extends[0]) == type("") let extends = t.extends[0] . '$'. class elseif type(t.extends[0]) == type({}) if has_key(t.extends[0], 'name') let className = t.extends[0].name elseif has_key(t.extends[0], 'clazz') let className = t.extends[0].clazz.name else let className = '' endif if !empty(className) let imports = javacomplete#imports#GetImports('imports_fqn', filekey) let fqn = javacomplete#imports#SearchSingleTypeImport(className, imports) let extends = fqn. '$'. a:class endif else let extends = '' endif else let extends = '' endif else let extends = '' endif if class == 'this' || class == 'super' || (has_key(t, 'fqn') && t.fqn == packagename. '.'. class) if &ft == 'jsp' let ci = javacomplete#collector#FetchClassInfo('javax.servlet.jsp.HttpJspPage') return ci endif call s:Log('A0. ' . class) if !empty(t) return javacomplete#util#Sort(s:Tree2ClassInfo(t)) else return {} endif endif for def in get(t, 'defs', []) if get(def, 'tag', '') == 'CLASSDEF' && get(def, 'name', '') == class return javacomplete#util#Sort(s:Tree2ClassInfo(def)) endif endfor let typename = class let typeArguments = '' let splittedType = s:SplitTypeArguments(typename) if type(splittedType) == type([]) let typename = splittedType[0] let typeArguments = splittedType[1] endif if stridx(typename, '$') > 0 let sc = split(typename, '\$') let typename = sc[0] let nested = '$'.sc[1] else let nested = '' endif let hasKeyword = javacomplete#util#HasKeyword(typename) if typename !~ '^\s*' . g:RE_QUALID . '\s*$' || hasKeyword call s:Log("no qualid: ". typename) return {} endif let collectedArguments = s:CollectTypeArguments(typeArguments, packagename, filekey) let fqns = s:CollectFQNs(typename, packagename, filekey, extends) for fqn in fqns let fqn = fqn . nested . collectedArguments let fqn = substitute(fqn, ' ', '', 'g') call javacomplete#collector#FetchClassInfo(fqn) let key = s:KeyInCache(fqn) if !empty(key) return get(g:JavaComplete_Cache[key], 'tag', '') == 'CLASSDEF' ? g:JavaComplete_Cache[key] : {} endif endfor return {} endfunction function! javacomplete#collector#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 endfunction function! javacomplete#collector#FetchClassInfo(fqn) call javacomplete#collector#FetchInfoFromServer(a:fqn, '-E') endfunction function! javacomplete#collector#FetchInfoFromServer(class, option) if has_key(g:JavaComplete_Cache, substitute(a:class, '\$', '.', 'g')) return g:JavaComplete_Cache[substitute(a:class, '\$', '.', 'g')] endif let res = javacomplete#server#Communicate(a:option, a:class, 'collector#FetchInfoFromServer') if res =~ "^{'" silent! let dict = eval(res) if !empty(dict) && type(dict)==type({}) for key in keys(dict) if !has_key(g:JavaComplete_Cache, key) if type(dict[key]) == type({}) let g:JavaComplete_Cache[substitute(key, '\$', '.', '')] = javacomplete#util#Sort(dict[key]) elseif type(dict[key]) == type([]) let g:JavaComplete_Cache[substitute(key, '\$', '.', '')] = sort(dict[key]) endif endif endfor else let b:errormsg = dict endif else let b:errormsg = res endif endfunction function! s:SplitTypeArguments(typename) if a:typename =~ g:RE_TYPE_WITH_ARGUMENTS let lbridx = stridx(a:typename, '<') let typeArguments = a:typename[lbridx + 1 : -2] let typename = a:typename[0 : lbridx - 1] return [typename, typeArguments] endif let lbridx = stridx(a:typename, '<') if lbridx > 0 let typename = a:typename[0 : lbridx - 1] return [typename, 0] endif return a:typename endfunction function! s:CollectTypeArguments(typeArguments, packagename, filekey) let collectedArguments = '' if !empty(a:typeArguments) let typeArguments = a:typeArguments let i = 0 let lbr = 0 while i < len(typeArguments) let c = typeArguments[i] if c == '<' let lbr += 1 elseif c == '>' let lbr -= 1 endif if c == ',' && lbr == 0 let typeArguments = typeArguments[0 : i - 1] . "<_split_>". typeArguments[i + 1 : -1] let i += 9 else let i += 1 endif endwhile for arg in split(typeArguments, "<_split_>") let argTypeArguments = '' if arg =~ g:RE_TYPE_WITH_ARGUMENTS let lbridx = stridx(arg, '<') let argTypeArguments = arg[lbridx : -1] let arg = arg[0 : lbridx - 1] endif if arg =~ g:RE_TYPE_ARGUMENT_EXTENDS let i = matchend(arg, g:RE_TYPE) let arg = arg[i+1 : -1] endif let fqns = s:CollectFQNs(arg, a:packagename, a:filekey, '') let collectedArguments .= '' if len(fqns) > 1 let collectedArguments .= '(' endif for fqn in fqns if len(fqn) > 0 let collectedArguments .= fqn. argTypeArguments. '|' endif endfor if len(fqns) > 1 let collectedArguments = collectedArguments[0:-2]. '),' else let collectedArguments = collectedArguments[0:-2]. ',' endif endfor if !empty(collectedArguments) let collectedArguments = '<'. collectedArguments[0:-2]. '>' endif endif return collectedArguments endfunction function! 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 type(def) == type([]) && len(def) == 1 let tmp = def[0] unlet def let def = tmp unlet tmp endif let tag = get(def, 'tag', '') if tag == 'METHODDEF' call add(def.n == t.name ? t.ctors : t.methods, def) elseif tag == 'VARDEF' call add(t.fields, def) elseif tag == 'CLASSDEF' call add(t.classes, t.fqn . '.' . def.name) endif unlet def endfor for line in reverse(getline(0, '.')) let matches = matchlist(line, g:RE_TYPE_DECL_HEAD. t.name) if len(matches) if matches[1] == 'interface' let t.interface = 1 elseif matches[1] == 'enum' let t.enum = 1 endif break endif endfor " convert type name in extends to fqn for class defined in source files if has_key(a:t, 'filepath') && a:t.filepath != javacomplete#GetCurrentFileKey() let filepath = a:t.filepath let packagename = get(g:JavaComplete_Files[filepath].unit, 'package', '') else let filepath = expand('%:p') let packagename = javacomplete#collector#GetPackageName() endif if !has_key(a:t, 'extends') let a:t.extends = ['java.lang.Object'] endif let extends = a:t.extends if has_key(a:t, 'implements') let extends += a:t.implements endif let i = 0 while i < len(extends) if type(extends[i]) == type("") && extends[i] == get(t, 'fqn', '') let i += 1 continue elseif type(extends[i]) == type({}) && extends[i].tag == 'ERRONEOUS' let i += 1 continue endif let type2str = java_parser#type2Str(extends[i]) let ci = javacomplete#collector#DoGetClassInfo(type2str, filepath, packagename) if type(ci) == type([]) let ci = [0] endif if has_key(ci, 'fqn') let extends[i] = ci.fqn endif let i += 1 endwhile let t.extends = javacomplete#util#uniq(extends) return t endfunction function! s:CollectFQNs(typename, packagename, filekey, extends) if len(split(a:typename, '\.')) > 1 return [a:typename] endif let brackets = stridx(a:typename, '[') let extra = '' if brackets >= 0 let typename = a:typename[0 : brackets - 1] let extra = a:typename[brackets : -1] else let typename = a:typename endif let imports = javacomplete#imports#GetImports('imports_fqn', a:filekey) let directFqn = javacomplete#imports#SearchSingleTypeImport(typename, imports) if !empty(directFqn) return [directFqn. extra] endif let fqns = [] call add(fqns, empty(a:packagename) ? a:typename : a:packagename . '.' . a:typename) let imports = javacomplete#imports#GetImports('imports_star', a:filekey) for p in imports call add(fqns, p . a:typename) endfor if !empty(a:extends) call add(fqns, a:extends) endif if typename != 'Object' call add(fqns, 'java.lang.Object') endif return fqns endfunction function! s:KeyInCache(fqn) let fqn = substitute(a:fqn, '<', '\\<', 'g') let fqn = substitute(fqn, '>', '\\>', 'g') let fqn = substitute(fqn, ']', '\\]', 'g') let fqn = substitute(fqn, '[', '\\[', 'g') let fqn = substitute(fqn, '\$', '.', 'g') let keys = keys(g:JavaComplete_Cache) let idx = match(keys, '\v'. fqn. '$') if idx >= 0 return keys[idx] endif return '' endfunction " a:1 - include related type function! javacomplete#collector#GetDeclaredClassName(var, ...) let var = javacomplete#util#Trim(a:var) call s:Log('get declared class name for: "' . var . '"') if var =~# '^\(this\|super\)$' return var endif " Special handling for objects in JSP if &ft == 'jsp' if get(g:J_JSP_BUILTIN_OBJECTS, a:var, '') != '' return g:J_JSP_BUILTIN_OBJECTS[a:var] endif return s:FastBackwardDeclarationSearch(a:var) endif let result = javacomplete#collector#SearchForName(var, 1, 1) let variable = get(result[2], -1, {}) if get(variable, 'tag', '') == 'VARDEF' if has_key(variable, 't') let splitted = split(variable.t, '\.') if len(splitted) == 1 let rootClassName = s:SearchForRootClassName(variable) if len(rootClassName) > 0 call insert(splitted, rootClassName) endif endif if len(splitted) > 1 let directFqn = javacomplete#imports#SearchSingleTypeImport(splitted[0], javacomplete#imports#GetImports('imports_fqn', javacomplete#GetCurrentFileKey())) if empty(directFqn) return variable.t endif else return variable.t endif return substitute(join(splitted, '.'), '\.', '\$', 'g') endif return java_parser#type2Str(variable.vartype) endif if has_key(variable, 't') return variable.t endif if a:0 > 0 let class = get(result[0], -1, {}) if get(class, 'tag', '') == 'CLASSDEF' if has_key(class, 'name') return class.name endif endif endif return '' endfunction function! s:FastBackwardDeclarationSearch(name) let lines = reverse(getline(0, '.')) for line in lines let splittedLine = split(line, ';') for l in splittedLine let l = javacomplete#util#Trim(l) let matches = matchlist(l, '^\('. g:RE_QUALID. '\)\s\+'. a:name) if len(matches) > 0 return matches[1] endif endfor endfor return '' endfunction function! s:SearchForRootClassName(variable) if has_key(a:variable, 'vartype') && type(a:variable.vartype) == type({}) if has_key(a:variable.vartype, 'tag') && a:variable.vartype.tag == 'TYPEAPPLY' if has_key(a:variable.vartype, 'clazz') && a:variable.vartype.clazz.tag == 'SELECT' let clazz = a:variable.vartype.clazz if has_key(clazz, 'selected') && has_key(clazz.selected, 'name') return clazz.selected.name endif endif endif endif return "" endfunction " first: return at once if found one. " fullmatch: 1 - equal, 0 - match beginning " return [types, methods, fields, vars] function! javacomplete#collector#SearchForName(name, first, fullmatch) let result = [[], [], [], []] if javacomplete#util#IsKeyword(a:name) return result endif let unit = javacomplete#parseradapter#Parse() let targetPos = java_parser#MakePos(line('.')-1, col('.')-1) let trees = javacomplete#parseradapter#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) elseif tree.tag == 'LAMBDA' let t = s:DetermineLambdaArguments(unit, tree, a:name) if !empty(t) call add(result[2], t) endif endif endfor if a:first && result != [[], [], [], []] | return result | endif " Accessible inherited members let type = get(javacomplete#parseradapter#SearchTypeAt(unit, targetPos), -1, {}) if !empty(type) let members = javacomplete#complete#complete#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] endif " static import let si = javacomplete#imports#SearchStaticImports(a:name, a:fullmatch) let result[0] += si[0] let result[1] += si[1] let result[2] += si[2] return result endfunction function! s:DetermineLambdaArguments(unit, ti, name) let nameInLambda = 0 let argIdx = 0 " argument index in method declaration let argPos = 0 if type(a:ti.args) == type({}) if a:name == a:ti.args.name let nameInLambda = 1 endif elseif type(a:ti.args) == type([]) for arg in a:ti.args if arg.name == a:name let nameInLambda = 1 let argPos = arg.pos break endif let argIdx += 1 endfor endif if !nameInLambda return {} endif let methods = [] let t = a:ti let type = '' if has_key(t, 'meth') && !empty(t.meth) let result = [] while 1 if has_key(t, 'meth') let t = t.meth elseif t.tag == 'SELECT' && has_key(t, 'selected') call add(result, t.name. '()') let t = t.selected elseif t.tag == 'IDENT' call add(result, t.name) break endif endwhile let items = reverse(result) let typename = javacomplete#collector#GetDeclaredClassName(items[0], 1) let ti = {} if (typename != '') if typename[1] == '[' || typename[-1:] == ']' let ti = g:J_ARRAY_TYPE_INFO elseif typename != 'void' && !javacomplete#util#IsBuiltinType(typename) let ti = javacomplete#collector#DoGetClassInfo(typename) endif else " it can be static request let ti = javacomplete#collector#DoGetClassInfo(items[0]) endif let ii = 1 while !empty(ti) && ii < len(items) - 1 " method invocation: "PrimaryExpr.method(parameters)[].|" if items[ii] =~ '^\s*' . g:RE_IDENTIFIER . '\s*(' let ti = javacomplete#collector#MethodInvocation(items[ii], ti, 0) endif let ii += 1 endwhile if has_key(ti, 'methods') let itemName = split(items[-1], '(')[0] for m in ti.methods if m.n == itemName call add(methods, m) endif endfor endif elseif has_key(t, 'stats') && !empty(t.stats) if t.stats.tag == 'VARDEF' let type = t.stats.t elseif t.stats.tag == 'RETURN' for ty in a:unit.types for def in ty.defs if def.tag == 'METHODDEF' if t.stats.pos >= def.body.pos && t.stats.endpos <= def.body.endpos let type = def.r endif endif endfor endfor endif endif for method in methods if a:ti.idx < len(method.p) let type = method.p[a:ti.idx] endif let res = s:GetLambdaParameterType(type, a:name, argIdx, argPos) if has_key(res, 'tag') return res endif endfor return s:GetLambdaParameterType(type, a:name, argIdx, argPos) endfunction " type should be FunctionInterface, and it contains only one abstract method function! s:GetLambdaParameterType(type, name, argIdx, argPos) let pType = '' if !empty(a:type) let matches = matchlist(a:type, '^java.util.function.Function<\(.*\)>') if len(matches) > 0 let types = split(matches[1], ',') if !empty(types) let type = javacomplete#scanner#ExtractCleanExpr(types[0]) return {'tag': 'VARDEF', 'name': type, 'type': {'tag': 'IDENT', 'name': type}, 'vartype': {'tag': 'IDENT', 'name': type, 'pos': a:argPos}, 'pos': a:argPos} endif else let functionalMembers = javacomplete#collector#DoGetClassInfo(a:type) if has_key(functionalMembers, 'methods') for m in functionalMembers.methods if javacomplete#util#CheckModifier(m.m, g:JC_MODIFIER_ABSTRACT) if a:argIdx < len(m.p) let pType = m.p[a:argIdx] break endif endif endfor if !empty(pType) return {'tag': 'VARDEF', 'name': a:name, 'type': {'tag': 'IDENT', 'name': pType}, 'vartype': {'tag': 'IDENT', 'name': pType, 'pos': a:argPos}, 'pos': a:argPos} endif endif endif endif return {} endfunction function! javacomplete#collector#MethodInvocation(expr, ti, itemkind) let subs = split(substitute(a:expr, '\s*\(' . g:RE_IDENTIFIER . '\)\s*\((.*\)', '\1;\2', ''), ';') " all methods matched if empty(a:ti) let methods = javacomplete#collector#SearchForName(subs[0], 0, 1)[1] elseif type(a:ti) == type({}) && get(a:ti, 'tag', '') == 'CLASSDEF' let methods = javacomplete#complete#complete#SearchMember(a:ti, subs[0], 1, a:itemkind, 1, 0, a:itemkind == 2)[1] else let methods = [] endif let method = s:DetermineMethod(methods, subs[1]) if !empty(method) return javacomplete#complete#complete#ArrayAccess(method.r, subs[0]) endif return {} endfunction " determine overloaded method by parameters count function! s:DetermineMethod(methods, parameters) let parameters = substitute(a:parameters, '(\(.*\))', '\1', '') let paramsCount = len(split(parameters, ',')) for m in a:methods if len(get(m, 'p', [])) == paramsCount return m endif endfor return get(a:methods, -1, {}) endfunction function! javacomplete#collector#CurrentFileInfo() let currentBuf = getline(1,'$') let base64Content = javacomplete#util#Base64Encode(join(currentBuf, "\n")) let ti = javacomplete#collector#DoGetClassInfo('this') if has_key(ti, 'name') let package = javacomplete#collector#GetPackageName(). '.'. ti.name call javacomplete#server#Communicate('-clear-from-cache', package, 's:CurrentFileInfo') let response = javacomplete#server#Communicate('-class-info-by-content -target '. package. ' -content', base64Content, 'CurrentFileInfo') if response =~ '^{' return eval(response) endif else call s:Log("`this` class parse error [CurrentFileInfo]") endif return {} endfunction " vim:set fdm=marker sw=2 nowrap: