" MNV filetype plugin file
" Language:		Java
" Maintainer:		Aliaksei Budavei <0x000c70 AT gmail DOT com>
" Former Maintainer:	Dan Sharp
" Repository:		https://github.com/zzzyxwvut/java-mnv.git
" Last Change:		2025 May 08

" Make sure the continuation lines below do not cause problems in
" compatibility mode.
let s:save_cpo = &cpo
set cpo-=C

if (exists("g:java_ignore_javadoc") || exists("g:java_ignore_markdown")) &&
	\ exists("*javaformat#RemoveCommonMarkdownWhitespace")
    delfunction javaformat#RemoveCommonMarkdownWhitespace
    unlet! g:loaded_javaformat
endif

if exists("b:did_ftplugin")
    let &cpo = s:save_cpo
    unlet s:save_cpo
    finish
endif

let b:did_ftplugin = 1

" For filename completion, prefer the .java extension over the .class
" extension.
set suffixes+=.class

" Set up "&define" and "&include".
let s:peek = ''

try
    " Since v7.3.1037.
    if 'ab' !~ 'a\@1<!b'
	let s:peek = string(strlen('instanceof') + 8)
    endif
catch /\<E59:/
endtry

" Treat "s:common" as a non-backtracking unit to avoid matching constructor
" declarations whose package-private headers are indistinguishable from method
" invocation.  Note that "[@-]" must not and "$" may not be in "&l:iskeyword".
let s:common = '\%(\%(\%(@\%(interface\)\@!\%(\K\k*\.\)*\K\k*\)\s\+\)*' .
	\ '\%(p\%(rivate\|rotected\|ublic\)\s\+\)\=\)\@>'
let s:types = '\%(\%(abstract\|final\|non-sealed\|s\%(ealed\|tatic\|trictfp\)\)\s\+\)*' .
	\ '\%(class\|enum\|@\=interface\|record\)\s\+\ze\K\k*\>'
let s:methods = '\%(\%(abstract\|default\|final\|native\|s\%(tatic\|trictfp\|ynchronized\)\)\s\+\)*' .
	\ '\%(<.\{-1,}>\s\+\)\=\%(\K\k*\.\)*\K\k*\s*\%(<.\{-1,}>\%(\s\|\[\)\@=\)\=\s*\%(\[\]\s*\)*' .
	\ '\s\+\ze\%(\<\%(assert\|case\|instanceof\|new\|return\|throw\|when\)\s\+\)\@' .
	\ s:peek . '<!\K\k*\s*('
let &l:define = printf('\C\m^\s*%s\%%(%s\|%s\)', s:common, s:types, s:methods)
let &l:include = '\C\m^\s*import\s\+\ze\%(\K\k*\.\)\+\K\k*;'
unlet s:methods s:types s:common s:peek

" Enable gf on import statements.  Convert . in the package
" name to / and append .java to the name, then search the path.
setlocal includeexpr=substitute(v:fname,'\\.','/','g')
setlocal suffixesadd=.java

" Clean up in case this file is sourced again.
unlet! s:zip_func_upgradable

"""" STRIVE TO REMAIN COMPATIBLE FOR AT LEAST MNV 7.0.

" Documented in ":help ft-java-plugin".
if exists("g:ftplugin_java_source_path") &&
		\ type(g:ftplugin_java_source_path) == type("")
    if filereadable(g:ftplugin_java_source_path)
	if exists("#zip") &&
		    \ g:ftplugin_java_source_path =~# '.\.\%(jar\|zip\)$'
	    if !exists("s:zip_files")
		let s:zip_files = {}
	    endif

	    let s:zip_files[bufnr('%')] = g:ftplugin_java_source_path
	    let s:zip_files[0] = g:ftplugin_java_source_path
	    let s:zip_func_upgradable = 1

	    function! JavaFileTypeZipFile() abort
		let @/ = substitute(v:fname, '\.', '\\/', 'g') . '.java'
		return get(s:zip_files, bufnr('%'), s:zip_files[0])
	    endfunction

	    " E120 for "inex=s:JavaFileTypeZipFile()" before v8.2.3900.
	    setlocal includeexpr=JavaFileTypeZipFile()
	    setlocal suffixesadd<
	endif
    else
	let &l:path = g:ftplugin_java_source_path . ',' . &l:path
    endif
endif

" Set 'formatoptions' to break comment lines but not other lines,
" and insert the comment leader when hitting <CR> or using "o".
setlocal formatoptions-=t formatoptions+=croql

" Set 'comments' to format Markdown Javadoc comments and dashed lists
" in other multi-line comments (it behaves just like C).
setlocal comments& comments^=:///,sO:*\ -,mO:*\ \ ,exO:*/

setlocal commentstring=//\ %s

" Change the :browse e filter to primarily show Java-related files.
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
    let  b:browsefilter="Java Files (*.java)\t*.java\n" .
		\	"Properties Files (*.prop*)\t*.prop*\n" .
		\	"Manifest Files (*.mf)\t*.mf\n"
    if has("win32")
	let b:browsefilter .= "All Files (*.*)\t*\n"
    else
	let b:browsefilter .= "All Files (*)\t*\n"
    endif
endif

"""" Support pre- and post-compiler actions for SpotBugs.
if (!empty(get(g:, 'spotbugs_properties', {})) ||
	\ !empty(get(b:, 'spotbugs_properties', {}))) &&
	\ filereadable($MNVRUNTIME . '/compiler/spotbugs.mnv')

    function! s:SpotBugsGetProperty(name, default) abort
	return get(
	    \ {s:spotbugs_properties_scope}spotbugs_properties,
	    \ a:name,
	    \ a:default)
    endfunction

    function! s:SpotBugsHasProperty(name) abort
	return has_key(
	    \ {s:spotbugs_properties_scope}spotbugs_properties,
	    \ a:name)
    endfunction

    function! s:SpotBugsGetProperties() abort
	return {s:spotbugs_properties_scope}spotbugs_properties
    endfunction

    " Work around ":bar"s and ":autocmd"s.
    function! JavaFileTypeExecuteActionOnce(cleanup_cmd, action_cmd) abort
	try
	    execute a:cleanup_cmd
	finally
	    execute a:action_cmd
	endtry
    endfunction

    if exists("b:spotbugs_properties")
	let s:spotbugs_properties_scope = 'b:'

	" Merge global entries, if any, in buffer-local entries, favouring
	" defined buffer-local ones.
	call extend(
	    \ b:spotbugs_properties,
	    \ get(g:, 'spotbugs_properties', {}),
	    \ 'keep')
    elseif exists("g:spotbugs_properties")
	let s:spotbugs_properties_scope = 'g:'
    endif

    let s:commands = {}

    for s:name in ['DefaultPreCompilerCommand',
	    \ 'DefaultPreCompilerTestCommand',
	    \ 'DefaultPostCompilerCommand']
	if s:SpotBugsHasProperty(s:name)
	    let s:commands[s:name] = remove(
		\ s:SpotBugsGetProperties(),
		\ s:name)
	endif
    endfor

    if s:SpotBugsHasProperty('compiler')
	" XXX: Postpone loading the script until all state, if any, has been
	" collected.
	if !empty(s:commands)
	    let g:spotbugs#state = {
		\ 'compiler': remove(s:SpotBugsGetProperties(), 'compiler'),
		\ 'commands': copy(s:commands),
	    \ }
	else
	    let g:spotbugs#state = {
		\ 'compiler': remove(s:SpotBugsGetProperties(), 'compiler'),
	    \ }
	endif

	" Merge default entries in global (or buffer-local) entries, favouring
	" defined global (or buffer-local) ones.
	call extend(
	    \ {s:spotbugs_properties_scope}spotbugs_properties,
	    \ spotbugs#DefaultProperties(),
	    \ 'keep')
    elseif !empty(s:commands)
	" XXX: Postpone loading the script until all state, if any, has been
	" collected.
	let g:spotbugs#state = {'commands': copy(s:commands)}
    endif

    unlet s:commands s:name
    let s:request = 0

    if s:SpotBugsHasProperty('PostCompilerAction')
	let s:request += 4
    endif

    if s:SpotBugsHasProperty('PreCompilerTestAction')
	let s:dispatcher = printf('call call(%s, [])',
	    \ string(s:SpotBugsGetProperties().PreCompilerTestAction))
	let s:request += 2
    endif

    if s:SpotBugsHasProperty('PreCompilerAction')
	let s:dispatcher = printf('call call(%s, [])',
	    \ string(s:SpotBugsGetProperties().PreCompilerAction))
	let s:request += 1
    endif

    " Adapt the tests for "s:FindClassFiles()" from "compiler/spotbugs.mnv".
    if (s:request == 3 || s:request == 7) &&
	    \ (!empty(s:SpotBugsGetProperty('sourceDirPath', [])) &&
		\ !empty(s:SpotBugsGetProperty('classDirPath', [])) &&
	    \ !empty(s:SpotBugsGetProperty('testSourceDirPath', [])) &&
		\ !empty(s:SpotBugsGetProperty('testClassDirPath', [])))
	function! s:DispatchAction(paths_action_pairs) abort
	    let name = expand('%:p')

	    for [paths, Action] in a:paths_action_pairs
		for path in paths
		    if name =~# (path . '.\{-}\.java\=$')
			call Action()
			return
		    endif
		endfor
	    endfor
	endfunction

	let s:dir_cnt = min([
	    \ len(s:SpotBugsGetProperties().sourceDirPath),
	    \ len(s:SpotBugsGetProperties().classDirPath)])
	let s:test_dir_cnt = min([
	    \ len(s:SpotBugsGetProperties().testSourceDirPath),
	    \ len(s:SpotBugsGetProperties().testClassDirPath)])

	" Do not break up path pairs with filtering!
	let s:dispatcher = printf('call s:DispatchAction(%s)',
	    \ string([[s:SpotBugsGetProperties().sourceDirPath[0 : s:dir_cnt - 1],
			\ s:SpotBugsGetProperties().PreCompilerAction],
		\ [s:SpotBugsGetProperties().testSourceDirPath[0 : s:test_dir_cnt - 1],
			\ s:SpotBugsGetProperties().PreCompilerTestAction]]))
	unlet s:test_dir_cnt s:dir_cnt
    endif

    if exists("s:dispatcher")
	function! s:ExecuteActions(pre_action, post_action) abort
	    try
		execute a:pre_action
	    catch /\<E42:/
		execute a:post_action
	    endtry
	endfunction
    endif

    if s:request
	if exists("b:spotbugs_syntax_once") || empty(join(getline(1, 8), ''))
	    let s:actions = [{'event': 'User'}]
	else
	    " XXX: Handle multiple FileType events when mnvrc contains more
	    " than one filetype setting for the language, e.g.:
	    "	:filetype plugin indent on
	    "	:autocmd BufRead,BufNewFile *.java setlocal filetype=java ...
	    " XXX: DO NOT ADD b:spotbugs_syntax_once TO b:undo_ftplugin !
	    let b:spotbugs_syntax_once = 1
	    let s:actions = [{
		    \ 'event': 'Syntax',
		    \ 'once': 1,
		\ }, {
		    \ 'event': 'User',
		\ }]
	endif

	for s:idx in range(len(s:actions))
	    if s:request == 7 || s:request == 6 || s:request == 5
		let s:actions[s:idx].cmd = printf('call s:ExecuteActions(%s, %s)',
		    \ string(s:dispatcher),
		    \ string(printf('compiler spotbugs | call call(%s, [])',
			\ string(s:SpotBugsGetProperties().PostCompilerAction))))
	    elseif s:request == 4
		let s:actions[s:idx].cmd = printf(
		    \ 'compiler spotbugs | call call(%s, [])',
		    \ string(s:SpotBugsGetProperties().PostCompilerAction))
	    elseif s:request == 3 || s:request == 2 || s:request == 1
		let s:actions[s:idx].cmd = printf('call s:ExecuteActions(%s, %s)',
		    \ string(s:dispatcher),
		    \ string('compiler spotbugs'))
	    else
		let s:actions[s:idx].cmd = ''
	    endif
	endfor

	if !exists("#java_spotbugs")
	    augroup java_spotbugs
	    augroup END
	endif

	" The events are defined in s:actions.
	silent! autocmd! java_spotbugs User <buffer>
	silent! autocmd! java_spotbugs Syntax <buffer>

	for s:action in s:actions
	    if has_key(s:action, 'once')
		execute printf('autocmd java_spotbugs %s <buffer> ' .
			\ 'call JavaFileTypeExecuteActionOnce(%s, %s)',
		    \ s:action.event,
		    \ string(printf('autocmd! java_spotbugs %s <buffer>',
			\ s:action.event)),
		    \ string(s:action.cmd))
	    else
		execute printf('autocmd java_spotbugs %s <buffer> %s',
		    \ s:action.event,
		    \ s:action.cmd)
	    endif
	endfor

	if s:SpotBugsHasProperty('PostCompilerActionExecutor') &&
		\ (s:request == 7 || s:request == 6 ||
		\ s:request == 5 || s:request == 4)
	    let s:augroup = s:SpotBugsGetProperty(
		\ 'augroupForPostCompilerAction',
		\ 'java_spotbugs_post')
	    let s:augroup = !empty(s:augroup) ? s:augroup : 'java_spotbugs_post'

	    for s:candidate in ['java_spotbugs_post', s:augroup]
		if !exists("#" . s:candidate)
		    execute printf('augroup %s | augroup END', s:candidate)
		endif
	    endfor

	    silent! autocmd! java_spotbugs_post User <buffer>

	    " Define a User ":autocmd" to define a once-only ShellCmdPost
	    " ":autocmd" that will invoke "PostCompilerActionExecutor" and let
	    " it decide whether to proceed with ":compiler spotbugs" etc.; and
	    " seek explicit synchronisation with ":doautocmd ShellCmdPost" by
	    " omitting "nested" for "java_spotbugs_post" and "java_spotbugs".
	    execute printf('autocmd java_spotbugs_post User <buffer> ' .
				\ 'call JavaFileTypeExecuteActionOnce(%s, %s)',
		\ string(printf('autocmd! %s ShellCmdPost <buffer>', s:augroup)),
		\ string(printf('autocmd %s ShellCmdPost <buffer> ' .
				\ 'call JavaFileTypeExecuteActionOnce(%s, %s)',
		    \ s:augroup,
		    \ string(printf('autocmd! %s ShellCmdPost <buffer>', s:augroup)),
		    \ string(printf('call call(%s, [%s])',
			\ string(s:SpotBugsGetProperties().PostCompilerActionExecutor),
			\ string(printf('compiler spotbugs | call call(%s, [])',
			    \ string(s:SpotBugsGetProperties().PostCompilerAction))))))))
	endif

	unlet! s:candidate s:augroup s:action s:actions s:idx s:dispatcher
    endif

    delfunction s:SpotBugsGetProperties
    delfunction s:SpotBugsHasProperty
    delfunction s:SpotBugsGetProperty
    unlet! s:request s:spotbugs_properties_scope
endif

function! JavaFileTypeCleanUp() abort
    setlocal suffixes< suffixesadd< formatoptions< comments< commentstring< path< includeexpr< include< define<
    unlet! b:browsefilter

    " The concatenated ":autocmd" removals may be misparsed as an ":autocmd".
    " A _once-only_ ShellCmdPost ":autocmd" is always a call-site definition.
    silent! autocmd! java_spotbugs User <buffer>
    silent! autocmd! java_spotbugs Syntax <buffer>
    silent! autocmd! java_spotbugs_post User <buffer>
endfunction

" Undo the stuff we changed.
let b:undo_ftplugin = 'call JavaFileTypeCleanUp() | delfunction JavaFileTypeCleanUp'

" See ":help mnv9-mix".
if !has("mnv9script")
    let &cpo = s:save_cpo
    unlet s:save_cpo
    finish
endif

if exists("s:zip_func_upgradable")
    delfunction! JavaFileTypeZipFile

    def! s:JavaFileTypeZipFile(): string
	@/ = substitute(v:fname, '\.', '\\/', 'g') .. '.java'
	return get(zip_files, bufnr('%'), zip_files[0])
    enddef

    setlocal includeexpr=s:JavaFileTypeZipFile()
    setlocal suffixesadd<
endif

if exists("*s:DispatchAction")
    def! s:DispatchAction(paths_action_pairs: list<list<any>>)
	const name: string = expand('%:p')

	for [paths: list<string>, Action: func: any] in paths_action_pairs
	    for path in paths
		if name =~# (path .. '.\{-}\.java\=$')
		    Action()
		    return
		endif
	    endfor
	endfor
    enddef
endif

" Restore the saved compatibility options.
let &cpo = s:save_cpo
unlet s:save_cpo
" mnv: fdm=syntax sw=4 ts=8 noet sta
