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 picocli.CommandLine;
41 import picocli.CommandLine.Help.Ansi;
42 import picocli.CommandLine.Option;
43 import picocli.CommandLine.ParseResult;
44
45 import java.awt.GraphicsEnvironment;
46 import java.io.ByteArrayOutputStream;
47 import java.io.UnsupportedEncodingException;
48 import java.net.URL;
49 import java.net.URLDecoder;
50 import java.nio.charset.StandardCharsets;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Enumeration;
54 import java.util.List;
55 import java.util.jar.JarEntry;
56 import java.util.jar.JarFile;
57 import java.util.logging.Level;
58 import java.util.logging.Logger;
59 import java.util.zip.ZipEntry;
60
61 import static java.lang.String.format;
62 import static java.util.Collections.sort;
63 import static picocli.CommandLine.usage;
64
65
66
67
68
69
70 public abstract class FFXCommand {
71
72
73
74
75 public static final Logger logger = Logger.getLogger(FFXCommand.class.getName());
76
77
78
79
80
81
82
83 public final Ansi color;
84
85
86
87
88 public String[] args;
89
90
91
92
93 public ParseResult parseResult = null;
94
95
96
97
98 @Option(
99 names = {"-V", "--version"},
100 versionHelp = true,
101 defaultValue = "false",
102 description = "Print the Force Field X version and exit.")
103 public boolean version;
104
105
106
107
108 @Option(
109 names = {"-h", "--help"},
110 usageHelp = true,
111 defaultValue = "false",
112 description = "Print command help and exit.")
113 public boolean help;
114
115
116
117
118 public FFXBinding binding;
119
120
121
122
123 public FFXCommand() {
124 this(new FFXBinding());
125 }
126
127
128
129
130
131
132 public FFXCommand(String[] args) {
133 this(new FFXBinding());
134 binding.setVariable("args", Arrays.asList(args));
135 }
136
137
138
139
140
141
142 public FFXCommand(FFXBinding binding) {
143 this.binding = binding;
144 if (GraphicsEnvironment.isHeadless()) {
145 color = Ansi.ON;
146 } else {
147 color = Ansi.OFF;
148 }
149 }
150
151
152
153
154
155
156 public void setBinding(FFXBinding binding) {
157 this.binding = binding;
158 }
159
160
161
162
163
164
165
166 public static Class<? extends FFXCommand> getCommand(String name) {
167 ClassLoader loader = FFXCommand.class.getClassLoader();
168 String pathName = name;
169 Class<?> command;
170 try {
171
172 command = loader.loadClass(pathName);
173 } catch (ClassNotFoundException e) {
174
175 pathName = "ffx.potential.commands." + name;
176 try {
177 command = loader.loadClass(pathName);
178 } catch (ClassNotFoundException e2) {
179
180 pathName = "ffx.algorithms.commands." + name;
181 try {
182 command = loader.loadClass(pathName);
183 } catch (ClassNotFoundException e2b) {
184 if (name.startsWith("xray.")) {
185
186 pathName = "ffx.xray.commands." + name.replaceAll("xray.", "");
187 } else if (name.startsWith("realspace.")) {
188 pathName = "ffx.realspace.commands." + name.replaceAll("realspace.", "");
189 } else {
190 pathName = "ffx." + name;
191 }
192 try {
193 command = loader.loadClass(pathName);
194 } catch (ClassNotFoundException e4) {
195 logger.warning(format(" %s was not found.", name));
196 return null;
197 }
198 }
199 }
200 }
201 return command.asSubclass(FFXCommand.class);
202 }
203
204
205
206
207
208
209
210 public static void listCommands(boolean logCommands, boolean logTestCommands) {
211 ClassLoader classLoader = ClassLoader.getSystemClassLoader();
212 try {
213 logger.info("\n Potential Package Commands:");
214 URL url = classLoader.getResource("ffx/potential");
215 listCommandsForPackage(url, logCommands, logTestCommands);
216 logger.info("\n Algorithms Package Commands:");
217 url = classLoader.getResource("ffx/algorithms");
218 listCommandsForPackage(url, logCommands, logTestCommands);
219 logger.info("\n Refinement Package Commands:");
220 url = classLoader.getResource("ffx/xray");
221 listCommandsForPackage(url, logCommands, logTestCommands);
222 } catch (Exception e) {
223 logger.info(" The FFX resource could not be found by the classloader.");
224 }
225 }
226
227
228
229
230
231
232
233
234 private static void listCommandsForPackage(URL commandURL, boolean logCommands, boolean logTestCommands) {
235 String commandPath = commandURL.getPath();
236 String ffx = commandPath.substring(5, commandURL.getPath().indexOf("!"));
237 List<String> commands = new ArrayList<>();
238 List<String> testCommands = new ArrayList<>();
239
240 try (JarFile jar = new JarFile(URLDecoder.decode(ffx, StandardCharsets.UTF_8))) {
241
242 Enumeration<JarEntry> enumeration = jar.entries();
243 while (enumeration.hasMoreElements()) {
244 ZipEntry zipEntry = enumeration.nextElement();
245 String className = zipEntry.getName();
246 if (className.startsWith("ffx")
247 && className.endsWith(".class")
248 && !className.contains("$")
249 && className.contains("commands")) {
250 className = className.replace("/", ".");
251 className = className.replace(".class", "");
252
253 className = className.replace("ffx.potential.commands.", "");
254 className = className.replace("ffx.algorithms.commands.", "");
255 className = className.replace("ffx.realspace.commands", "realspace");
256 className = className.replace("ffx.xray.commands", "xray");
257 if (className.toUpperCase().contains("TEST")) {
258 testCommands.add(className);
259 } else {
260 commands.add(className);
261 }
262 }
263 }
264 } catch (Exception e) {
265 logger.info(format(" The %s resource could not be decoded.", commandPath));
266 return;
267 }
268
269
270 sort(commands);
271 sort(testCommands);
272
273
274 if (logTestCommands) {
275 for (String command : testCommands) {
276 logger.info(" " + command);
277 }
278 }
279 if (logCommands) {
280 for (String command : commands) {
281 logger.info(" " + command);
282 }
283 }
284 }
285
286
287
288
289
290
291 public String helpString() {
292 try {
293 StringOutputStream sos = new StringOutputStream(new ByteArrayOutputStream());
294 usage(this, sos, color);
295 return " " + sos;
296 } catch (UnsupportedEncodingException e) {
297 logger.log(Level.WARNING, e.toString());
298 return null;
299 }
300 }
301
302
303
304
305
306
307 public boolean init() {
308
309 Object arguments = binding.getVariable("args");
310
311 if (arguments instanceof List<?> list) {
312 int numArgs = list.size();
313 args = new String[numArgs];
314 for (int i = 0; i < numArgs; i++) {
315 args[i] = (String) list.get(i);
316 }
317 } else if (arguments instanceof String[]) {
318 args = (String[]) arguments;
319 } else if (arguments instanceof String) {
320 args = new String[]{(String) arguments};
321 } else {
322 args = new String[0];
323 }
324
325 CommandLine commandLine = new CommandLine(this);
326 try {
327 parseResult = commandLine.parseArgs(args);
328 } catch (CommandLine.UnmatchedArgumentException uae) {
329 logger.warning(
330 " 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).");
331 throw uae;
332 }
333
334
335 if (help) {
336 logger.info(helpString());
337 return false;
338 }
339
340
341
342
343 return !version;
344 }
345
346
347
348
349
350
351
352 public FFXCommand run() {
353 logger.info(helpString());
354 return this;
355 }
356 }