1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.utilities;
39
40 import static java.lang.String.format;
41 import static java.util.Collections.sort;
42 import static picocli.CommandLine.usage;
43
44 import java.awt.GraphicsEnvironment;
45 import java.io.ByteArrayOutputStream;
46 import java.io.UnsupportedEncodingException;
47 import java.net.URL;
48 import java.net.URLDecoder;
49 import java.nio.charset.StandardCharsets;
50 import java.util.ArrayList;
51 import java.util.Enumeration;
52 import java.util.List;
53 import java.util.jar.JarEntry;
54 import java.util.jar.JarFile;
55 import java.util.logging.Level;
56 import java.util.logging.Logger;
57 import java.util.zip.ZipEntry;
58 import picocli.CommandLine;
59 import picocli.CommandLine.Help.Ansi;
60 import picocli.CommandLine.Option;
61 import picocli.CommandLine.ParseResult;
62
63
64
65
66
67
68 public abstract class FFXCommand {
69
70
71 public static final Logger logger = Logger.getLogger(FFXCommand.class.getName());
72
73
74
75
76
77
78
79 public final Ansi color;
80
81
82 public String[] args;
83
84
85 public ParseResult parseResult = null;
86
87 private final FFXContext ffxContext;
88
89
90 @Option(
91 names = {"-V", "--version"},
92 versionHelp = true,
93 defaultValue = "false",
94 description = "Print the Force Field X version and exit.")
95 public boolean version;
96
97
98 @Option(
99 names = {"-h", "--help"},
100 usageHelp = true,
101 defaultValue = "false",
102 description = "Print command help and exit.")
103 public boolean help;
104
105
106
107
108
109
110 public FFXCommand(FFXContext ffxContext) {
111 this.ffxContext = ffxContext;
112 if (GraphicsEnvironment.isHeadless()) {
113 color = Ansi.ON;
114 } else {
115 color = Ansi.OFF;
116 }
117 }
118
119
120
121
122
123
124 public FFXContext getFfxContext() {
125 return ffxContext;
126 }
127
128
129
130
131
132
133
134 public static Class<? extends FFXCommand> getCommand(String name) {
135 ClassLoader loader = FFXCommand.class.getClassLoader();
136 String pathName = name;
137 Class<?> script;
138 try {
139
140 script = loader.loadClass(pathName);
141 } catch (ClassNotFoundException e) {
142
143 pathName = "ffx.potential.commands." + name;
144 try {
145 script = loader.loadClass(pathName);
146 } catch (ClassNotFoundException e2) {
147
148 pathName = "ffx.algorithms.commands." + name;
149 try {
150 script = loader.loadClass(pathName);
151 } catch (ClassNotFoundException e3) {
152 if (name.startsWith("xray.")) {
153
154 pathName = "ffx.xray.commands." + name.replaceAll("xray.", "");
155 } else if (name.startsWith("realspace.")) {
156 pathName = "ffx.realspace.commands." + name.replaceAll("realspace.", "");
157 } else {
158 pathName = "ffx." + name;
159 }
160 try {
161 script = loader.loadClass(pathName);
162 } catch (ClassNotFoundException e4) {
163 logger.warning(format(" %s was not found.", name));
164 return null;
165 }
166 }
167 }
168 }
169 return script.asSubclass(FFXCommand.class);
170 }
171
172
173
174
175
176
177
178 public static void listCommands(boolean logCommands, boolean logTestCommands) {
179 ClassLoader classLoader = ClassLoader.getSystemClassLoader();
180 String location = "ffx";
181 URL scriptURL = classLoader.getResource(location);
182 if (scriptURL == null) {
183 logger.info(format(" The %s resource could not be found by the classloader.", location));
184 return;
185 }
186 String scriptPath = scriptURL.getPath();
187 String ffx = scriptPath.substring(5, scriptURL.getPath().indexOf("!"));
188 List<String> commands = new ArrayList<>();
189 List<String> testCommands = new ArrayList<>();
190 try (JarFile jar = new JarFile(URLDecoder.decode(ffx, StandardCharsets.UTF_8))) {
191
192 Enumeration<JarEntry> enumeration = jar.entries();
193 while (enumeration.hasMoreElements()) {
194 ZipEntry zipEntry = enumeration.nextElement();
195 String className = zipEntry.getName();
196 if (className.startsWith("ffx")
197 && className.contains("commands")
198 && className.endsWith(".class")
199 && !className.contains("$")) {
200 className = className.replace("/", ".");
201 className = className.replace(".class", "");
202
203 className = className.replace("ffx.potential.commands.", "");
204 className = className.replace("ffx.algorithms.commands.", "");
205 className = className.replace("ffx.realspace.commands", "realspace");
206 className = className.replace("ffx.xray.commands", "xray");
207 if (className.toUpperCase().contains("TEST")) {
208 testCommands.add(className);
209 } else {
210 commands.add(className);
211 }
212 }
213 }
214 } catch (Exception e) {
215 logger.info(format(" The %s resource could not be decoded.", location));
216 return;
217 }
218
219
220 sort(commands);
221 sort(testCommands);
222
223
224 if (logTestCommands) {
225 for (String script : testCommands) {
226 logger.info(" " + script);
227 }
228 }
229 if (logCommands) {
230 for (String script : commands) {
231 logger.info(" " + script);
232 }
233 }
234 }
235
236
237
238
239
240
241 public String helpString() {
242 try {
243 StringOutputStream sos = new StringOutputStream(new ByteArrayOutputStream());
244 usage(this, sos, color);
245 return " " + sos;
246 } catch (UnsupportedEncodingException e) {
247 logger.log(Level.WARNING, e.toString());
248 return null;
249 }
250 }
251
252
253
254
255
256
257 public boolean init() {
258
259
260 Object arguments = null;
261 try {
262 arguments = ffxContext.getVariable("args");
263 } catch (Exception e) {
264 logger.info(" Exception loading command line args:" + arguments);
265 }
266
267 if (arguments instanceof List<?> list) {
268 int numArgs = list.size();
269 args = new String[numArgs];
270 for (int i = 0; i < numArgs; i++) {
271 args[i] = (String) list.get(i);
272 }
273 } else {
274 args = (String[]) arguments;
275 }
276
277 CommandLine commandLine = new CommandLine(this);
278 try {
279 parseResult = commandLine.parseArgs(args);
280 } catch (CommandLine.UnmatchedArgumentException uae) {
281 logger.warning(
282 " The usual source of this exception is when long-form arguments (such as --uaA) are only preceded by one dash (such as -uaA, which is an error).");
283 throw uae;
284 }
285
286
287 if (help) {
288 logger.info(helpString());
289 return false;
290 }
291
292
293
294
295 return !version;
296 }
297
298
299
300
301
302
303 public FFXCommand run() {
304 logger.info(helpString());
305 return this;
306 }
307 }