1
2
3
4
5 package oshi.software.os.mac;
6
7 import static oshi.jna.platform.mac.CoreFoundation.CFDateFormatterStyle.kCFDateFormatterShortStyle;
8
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.LinkedHashSet;
13 import java.util.LinkedHashMap;
14 import java.util.Map;
15 import java.util.Set;
16
17 import com.sun.jna.platform.mac.CoreFoundation.CFStringRef;
18 import com.sun.jna.platform.mac.CoreFoundation.CFIndex;
19
20 import oshi.jna.platform.mac.CoreFoundation;
21 import oshi.jna.platform.mac.CoreFoundation.CFDateFormatter;
22 import oshi.jna.platform.mac.CoreFoundation.CFDateFormatterStyle;
23 import oshi.jna.platform.mac.CoreFoundation.CFLocale;
24 import oshi.software.os.ApplicationInfo;
25 import oshi.util.Constants;
26 import oshi.util.ExecutingCommand;
27 import oshi.util.ParseUtil;
28
29 public final class MacInstalledApps {
30
31 private static final String COLON = ":";
32 private static final CoreFoundation CF = CoreFoundation.INSTANCE;
33
34 private MacInstalledApps() {
35 }
36
37 public static List<ApplicationInfo> queryInstalledApps() {
38 List<String> output = ExecutingCommand.runNative("system_profiler SPApplicationsDataType");
39 return parseMacAppInfo(output);
40 }
41
42 private static List<ApplicationInfo> parseMacAppInfo(List<String> lines) {
43 Set<ApplicationInfo> appInfoSet = new LinkedHashSet<>();
44 String appName = null;
45 Map<String, String> appDetails = null;
46 boolean collectingAppDetails = false;
47 String dateFormat = getLocaleDateTimeFormat(kCFDateFormatterShortStyle);
48
49 for (String line : lines) {
50 line = line.trim();
51
52
53 if (line.endsWith(COLON)) {
54
55 if (appName != null && !appDetails.isEmpty()) {
56 appInfoSet.add(createAppInfo(appName, appDetails, dateFormat));
57 }
58
59
60 appName = line.substring(0, line.length() - 1);
61 appDetails = new HashMap<>();
62 collectingAppDetails = true;
63 continue;
64 }
65
66
67 if (collectingAppDetails && line.contains(COLON)) {
68 int colonIndex = line.indexOf(COLON);
69 String key = line.substring(0, colonIndex).trim();
70 String value = line.substring(colonIndex + 1).trim();
71 appDetails.put(key, value);
72 }
73 }
74
75 return new ArrayList<>(appInfoSet);
76 }
77
78 private static ApplicationInfo createAppInfo(String name, Map<String, String> details, String dateFormat) {
79 String obtainedFrom = ParseUtil.getValueOrUnknown(details, "Obtained from");
80 String signedBy = ParseUtil.getValueOrUnknown(details, "Signed by");
81 String vendor = (obtainedFrom.equals("Identified Developer")) ? signedBy : obtainedFrom;
82
83 String lastModified = details.getOrDefault("Last Modified", Constants.UNKNOWN);
84 long lastModifiedEpoch = ParseUtil.parseDateToEpoch(lastModified, dateFormat);
85
86
87 Map<String, String> additionalInfo = new LinkedHashMap<>();
88 additionalInfo.put("Kind", ParseUtil.getValueOrUnknown(details, "Kind"));
89 additionalInfo.put("Location", ParseUtil.getValueOrUnknown(details, "Location"));
90 additionalInfo.put("Get Info String", ParseUtil.getValueOrUnknown(details, "Get Info String"));
91
92 return new ApplicationInfo(name, ParseUtil.getValueOrUnknown(details, "Version"), vendor, lastModifiedEpoch,
93 additionalInfo);
94 }
95
96 private static String getLocaleDateTimeFormat(CFDateFormatterStyle style) {
97 CFIndex styleIndex = style.index();
98 CFLocale locale = CF.CFLocaleCopyCurrent();
99 try {
100 CFDateFormatter formatter = CF.CFDateFormatterCreate(null, locale, styleIndex, styleIndex);
101 if (formatter == null) {
102 return "";
103 }
104 try {
105 CFStringRef format = CF.CFDateFormatterGetFormat(formatter);
106 return (format == null) ? "" : format.stringValue();
107 } finally {
108 CF.CFRelease(formatter);
109 }
110 } finally {
111 CF.CFRelease(locale);
112 }
113 }
114 }