Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
382fd0a
feat: 重构SettingPage的排版
futurw4v Feb 17, 2026
483edf6
chore(setting_page): 将“下载”改为“设置”
futurw4v Feb 17, 2026
429af46
feat: 重构ThemePage
futurw4v Feb 17, 2026
a285078
feat: 重构JavaPage
futurw4v Feb 18, 2026
0131ba9
feat: 使设置页面的标题对齐设置
futurw4v Feb 18, 2026
4599aff
refactor(JavaPage): 清理代码,优化代码结构和命名
futurw4v Feb 18, 2026
cbc3262
refactor: 用FutureBuilder重构LogViewerPage
futurw4v Feb 18, 2026
91eaddf
refactor(LogViewerPage): 引入DateFormat格式化时间,重新设置代码格式
futurw4v Feb 18, 2026
a88c1df
feat(LogViewerPage): 将标题行按钮组右对齐,更改卡片样式,添加视觉细节
futurw4v Feb 18, 2026
59f0d80
refactor(about): 整理代码格式
futurw4v Feb 18, 2026
1f96374
feat(about): 更换卡片样式,更新版权年份
futurw4v Feb 18, 2026
2ac4ee7
feat(log_setting): 更换卡片样式,优化间距
futurw4v Feb 18, 2026
e7cc7a8
fix: 移除多余括号,修复编译错误
futurw4v Feb 19, 2026
cc52a69
refactor: 用kDefaultPadding替换相应常量
futurw4v Feb 20, 2026
04a4b1f
feat(log_viewer): 为显示日志等级的容器添加内边距
futurw4v Feb 20, 2026
cc8a470
perf: 添加const标识符
futurw4v Feb 20, 2026
eef4d74
fix(log_viewer): 清除日志后重新加载列表
futurw4v Feb 20, 2026
79c4ce4
fix(java_manager): 使用正确的PATH分隔符扫描Java可执行文件
futurw4v Feb 20, 2026
29d0380
refactor(java_manager): 整理代码,提取与JavaPage相同的部分
futurw4v Feb 20, 2026
19c3567
feat(java.dart): 优化Java卡片视觉,优化代码
futurw4v Feb 24, 2026
545e539
feat(java_manage): 为查找java添加递归,整理代码
futurw4v Mar 7, 2026
a6a6ac8
feat(java.dart): 提取通用创建Card代码,为当前java添加Chip便于分辨
futurw4v Mar 7, 2026
4f94be1
feat: 添加LazyLoadIndexedStack使页面懒加载
futurw4v Mar 15, 2026
9b92579
feat(log_viewer): 使日志从新到旧显示
futurw4v Mar 15, 2026
189464d
chore(java_manage): 移除无用日志
futurw4v Mar 15, 2026
ac5be15
Merge branch 'main' into feature/setting_page
futurw4v Mar 15, 2026
ae7433d
fix(theme): 优化代码,修复主题切换不生效,优化颜色选中视觉风格
futurw4v Mar 21, 2026
f81f8f0
fix(log_viewer): 将顶部操作按钮与标题拆分并让部分按钮与FutureBuilder数据同步,优化代码格式
futurw4v Mar 28, 2026
9934af4
refactor(java): 更改systemJavaInfo的空安全处理
futurw4v Mar 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 212 additions & 52 deletions lib/function/java/java_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,86 @@ import 'dart:io';
import 'package:fml/function/log.dart';
import 'package:fml/function/java/models/java_info.dart';
import 'package:fml/function/java/models/java_runtime.dart';
import 'package:path/path.dart' as path;

class JavaManager {
JavaManager._();

// 寻找 Java 可执行文件
static Future<List<JavaRuntime>> searchPotentialJavaExecutables() async {
///
/// Java 可执行文件名称
///
static String kJavaExecutableName = Platform.isWindows ? 'java.exe' : 'java';
Comment thread
futurw4v marked this conversation as resolved.

static final RegExp _vendorVersionRegExp = RegExp(
r'(?:(OpenJDK|java|IBM|AdoptOpenJDK|Microsoft).*?)?version\s+"([^"]+)"',
caseSensitive: false,
);

static final RegExp _fallbackVersionRegExp = RegExp(r'"([0-9._-]+)"');

///
/// 寻找 Java 可执行文件
///
static Future<List<JavaRuntime>> searchPotentialJavaExecutables({
int searchDepth = 4,
}) async {
final Set<String> found = {};
final List<JavaRuntime> result = [];

// PATH
final pathEntries = Platform.environment['PATH']?.split(Platform.pathSeparator) ?? [];
final pathSeparator = Platform.isWindows ? ';' : ':';
final pathEntries =
Platform.environment['PATH']?.split(pathSeparator) ?? [];

for (final entry in pathEntries) {
if (entry.trim().isEmpty) continue;
final javaPath = _join(entry, _javaExecutableName());

final javaPath = _join(entry, kJavaExecutableName);

if (await File(javaPath).exists()) {
found.add(await File(javaPath).resolveSymbolicLinks());
}
}

// 常用系统目录
final List<Directory> candidates = [];

if (Platform.isWindows) {
final env = Platform.environment;
final prog = env['ProgramFiles'] ?? 'C:\\Program Files';
final progx86 = env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)';

candidates.add(Directory(prog));
candidates.add(Directory(progx86));
} else if (Platform.isLinux) {
candidates.add(Directory('/usr/java'));
candidates.add(Directory('/usr/lib/jvm'));
candidates.add(Directory('/usr/lib32/jvm'));
candidates.add(Directory('/usr/lib64/jvm'));

final home = Platform.environment['HOME'];
if (home != null) candidates.add(Directory('$home/.sdkman/candidates/java'));

if (home != null) {
candidates.add(Directory('$home/.sdkman/candidates/java'));
}
} else if (Platform.isMacOS) {
candidates.add(Directory('/Library/Java/JavaVirtualMachines'));

final home = Platform.environment['HOME'];
if (home != null) candidates.add(Directory('$home/Library/Java/JavaVirtualMachines'));

if (home != null) {
candidates.add(Directory('$home/Library/Java/JavaVirtualMachines'));
}
}

// 用户jdks
final home = Platform.environment['HOME'];

if (home != null) candidates.add(Directory('$home/.jdks'));

for (final dir in candidates) {
if (!await dir.exists()) continue;

try {
await for (final entry in dir.list(followLinks: false)) {
if (entry is Directory) {
Expand All @@ -61,10 +99,12 @@ class JavaManager {
LogUtil.log('查找 Java 可执行文件时出错:$e', level: 'WARN');
}
}

// 同时检查每个候选目录下常见的顶级 JDK 名称
for (final exe in found) {
try {
final info = await _probeJavaExecutable(exe);

if (info != null) {
final isJdk = await _looksLikeJdk(exe);
result.add(JavaRuntime(info: info, executable: exe, isJdk: isJdk));
Expand All @@ -74,92 +114,199 @@ class JavaManager {
}
}

// 通过可执行路径确保唯一性
final unique = <String, JavaRuntime>{};
for (final r in result) {
unique[r.executable] = r;
final roots = <Directory>[];

if (Platform.isWindows) {
// Windows枚举
for (int i = 0; i < 26; i++) {
// CharCode 68(排除A,B,C)
final drive = '${String.fromCharCode(68 + i)}:\\';
final dir = Directory(drive);

try {
if (dir.existsSync()) {
roots.add(dir);
}
} catch (_) {
// 忽略无法访问的驱动器
}
}

//final stopwatch = Stopwatch()..start();
for (final rootDir in roots) {
final javaRuntimes = await _searchJavaInDirRecursive(
dir: rootDir,
searchDepth: searchDepth,
);

result.addAll(javaRuntimes);
}
//stopwatch.stop();

//print('timeee: ${stopwatch.elapsedMilliseconds} ms, $result');
Comment thread
futurw4v marked this conversation as resolved.
}
return unique.values.toList();
}

// 路径拼接
static String _join(String a, String b) {
if (a.endsWith(Platform.pathSeparator)) return '$a$b';
return a + Platform.pathSeparator + b;
// 去重返回(按 executable 路径去重)
final Map<String, JavaRuntime> uniqueByExecutable = {};

for (final runtime in result) {
// 后出现的同一路径会覆盖先前的,确保最终列表中每个 executable 唯一
uniqueByExecutable[runtime.executable] = runtime;
}

return uniqueByExecutable.values.toList();
}

// Java 可执行文件名称
static String _javaExecutableName() {
return Platform.isWindows ? 'java.exe' : 'java';
///
/// 递归搜索Java运行时
///
/// [dir] 要搜索的根目录
/// [searchDepth] 最大允许的递归深度
/// [currentDepth] 当前递归深度(内部调用使用)
///
static Future<List<JavaRuntime>> _searchJavaInDirRecursive({
required Directory dir,
required int searchDepth,
int currentDepth = 0,
}) async {
List<JavaRuntime> result = [];

if (currentDepth > searchDepth) return result;

try {
// 检查当前目录下的文件
final dirs = dir.list(followLinks: false);

await for (final entity in dirs) {
final name = path.basename(entity.path);

// 排除 . 开头目录
if (name.startsWith('.')) continue;

if (entity is File) {
final lowerFileName = name.toLowerCase();

if (lowerFileName == kJavaExecutableName) {
// 创建JavaRuntime逻辑
final exePath = entity.path;
final info = await _probeJavaExecutable(exePath);

if (info != null) {
final isJdk = await _looksLikeJdk(exePath);

result.add(
JavaRuntime(info: info, executable: exePath, isJdk: isJdk),
);
}
}
} else if (entity is Directory) {
// 递归搜索子目录(深度+1)
result.addAll(
await _searchJavaInDirRecursive(
dir: entity,
currentDepth: currentDepth + 1,
searchDepth: searchDepth,
),
);
}
}
} catch (e) {
// 忽略权限错误或无法访问的目录
}

return result;
}

// 可能的可执行文件路径
///
/// 可能的可执行文件路径
///
static List<String> _possibleExecutablePaths(String javaHome) {
final List<String> probes = [];

if (Platform.isMacOS) {
probes.add('$javaHome/jre.bundle/Contents/Home/bin/${_javaExecutableName()}');
probes.add('$javaHome/Contents/Home/bin/${_javaExecutableName()}');
probes.add('$javaHome/jre.bundle/Contents/Home/bin/$kJavaExecutableName');

probes.add('$javaHome/Contents/Home/bin/$kJavaExecutableName');
}
probes.add('$javaHome/bin/${_javaExecutableName()}');
probes.add('$javaHome/jre/bin/${_javaExecutableName()}');

probes.add('$javaHome/bin/$kJavaExecutableName');
probes.add('$javaHome/jre/bin/$kJavaExecutableName');
return probes;
}

// 检查可执行文件是否看为 JDK(存在 javac)
///
/// 检查可执行文件是否看为 JDK(存在 javac)
///
static Future<bool> _looksLikeJdk(String exe) async {
try {
final bin = File(exe).parent;
final javac = File('${bin.path}${Platform.pathSeparator}javac${Platform.isWindows ? '.exe' : ''}');
final javac = File(
'${bin.path}${Platform.pathSeparator}javac${Platform.isWindows ? '.exe' : ''}',
);

return await javac.exists();
} catch (_) {
return false;
}
}

// Java 可执行文件信息
///
/// Java 可执行文件信息
///
static Future<JavaInfo?> _probeJavaExecutable(String exe) async {
// 首先尝试“java -version”
try {
final proc = await Process.start(exe, ['-version']);
final out = await proc.stderr.transform(SystemEncoding().decoder).join();
await proc.exitCode;
final parsed = _parseVersionOutput(out);

final parsed = parseVersionOutput(out);

if (parsed != null) {
return JavaInfo(
version: parsed['version']!,
vendor: parsed['vendor'],
path: exe,
os: _currentOsName(),
os: Platform.operatingSystem,
arch: Platform.version,
);
}
} catch (e) {
LogUtil.log('执行 "$exe -version" 时出错:$e', level: 'WARN');
}

// 尝试读取父目录中的发布文件
try {
final bin = File(exe).parent;
final javaHome = bin.parent.path;
final release = File('$javaHome${Platform.pathSeparator}release');

if (await release.exists()) {
final lines = await release.readAsLines();
final map = <String, String>{};
for (final l in lines) {
final idx = l.indexOf('=');
if (idx > 0) {
final k = l.substring(0, idx).trim();
var v = l.substring(idx + 1).trim();
if (v.startsWith('"') && v.endsWith('"')) v = v.substring(1, v.length - 1);
map[k] = v;

for (final line in lines) {
final index = line.indexOf('=');
if (index > 0) {
final key = line.substring(0, index).trim();
var value = line.substring(index + 1).trim();

if (value.startsWith('"') && value.endsWith('"')) {
value = value.substring(1, value.length - 1);
}

map[key] = value;
}
}

final version = map['JAVA_VERSION'] ?? map['IMPLEMENTOR_VERSION'] ?? '';

if (version.isNotEmpty) {
return JavaInfo(
version: version,
vendor: map['IMPLEMENTOR'] ?? map['JAVA_VENDOR'],
path: exe,
os: _currentOsName(),
os: Platform.operatingSystem,
arch: Platform.version,
);
}
Expand All @@ -170,34 +317,47 @@ class JavaManager {
return null;
}

// 解析 "java -version" 输出
static Map<String, String?>? _parseVersionOutput(String out) {
final lines = out.split('\n');
for (final l in lines) {
final s = l.trim();
if (s.isEmpty) continue;
final matches = RegExp(r'(?:(OpenJDK|java|IBM|AdoptOpenJDK|Microsoft).*?)?version\s+"([^"]+)"', caseSensitive: false).firstMatch(s);
///
/// 解析 "java -version" 输出
///
static Map<String, String?>? parseVersionOutput(String output) {
// 分割每行
final lines = output.split('\n');

for (final line in lines) {
final trimmedLine = line.trim();

if (trimmedLine.isEmpty) continue;

final matches = _vendorVersionRegExp.firstMatch(trimmedLine);

if (matches != null) {
String? vendor;

if (matches.group(1) == 'java') {
vendor = 'Oracle';
} else {
vendor = matches.group(1);
}

final version = matches.group(2);
return {'version': version ?? '', 'vendor': vendor};
}
final alt = RegExp(r'"([0-9._-]+)"').firstMatch(s);
if (alt != null) return {'version': alt.group(1) ?? '', 'vendor': null};

final fallbackMatch = _fallbackVersionRegExp.firstMatch(line);

if (fallbackMatch != null) {
return {'version': fallbackMatch.group(1) ?? '', 'vendor': null};
}
}
return null;
}

// 当前操作系统名称
static String _currentOsName() {
if (Platform.isWindows) return 'windows';
if (Platform.isMacOS) return 'macos';
if (Platform.isLinux) return 'linux';
return 'unknown';
///
/// 路径拼接
///
static String _join(String a, String b) {
if (a.endsWith(Platform.pathSeparator)) return '$a$b';
return a + Platform.pathSeparator + b;
}
}
Loading
Loading