/** * Output usage message flag listing. * * @param subclassOpts array containing flag and description pairs */ protected void outputFlags(String[][] subclassOpts) { // Build options list final ArrayList<String[]> optionList = new ArrayList<>(); // Add options directly supported by AbstractMain optionList.addAll( Arrays.<String[]>asList( new String[][] { {"--classpath, -cp path", "Append to the classpath (useful with `java -jar ...')"}, {"--read-only, -ro", "Disallow database modifications"}, {"--new-schema", "Allow recording of a new database schema version"}, { "--schema-version, -v num", "Specify database schema version (default highest recorded)" }, { "--model-pkg package", "Scan for @JSimpleClass model classes under Java package (=> JSimpleDB mode)" }, { "--type-pkg package", "Scan for @JFieldType types under Java package to register custom types" }, {"--pkg, -p package", "Equivalent to `--model-pkg package --type-pkg package'"}, {"--help, -h", "Show this help message"}, {"--verbose", "Show verbose error messages"}, })); // Add options supported by the various key/value implementations final KVImplementation[] kvs = KVImplementation.getImplementations(); for (KVImplementation kv : kvs) optionList.addAll(Arrays.<String[]>asList(kv.getCommandLineOptions())); // Add options supported by subclass if (subclassOpts != null) optionList.addAll(Arrays.<String[]>asList(subclassOpts)); // Sort options Collections.sort( optionList, new Comparator<String[]>() { @Override public int compare(String[] opt1, String[] opt2) { return opt1[0].compareTo(opt2[0]); } }); // Display all supported options int width = 0; for (String[] opt : optionList) width = Math.max(width, opt[0].length()); for (String[] opt : optionList) System.err.println(String.format(" %-" + width + "s %s", opt[0], opt[1])); // Display additional usage text for (KVImplementation kv : kvs) { final String usageText = kv.getUsageText(); if (usageText != null) System.err.println(usageText.trim()); } }
/** * Parse command line options. * * @param params command line parameters * @return -1 to proceed, otherwise process exit value */ public int parseOptions(ArrayDeque<String> params) { // Special logic to automate the demo when no flags are given if (params.isEmpty() && DEMO_XML_FILE.exists() && DEMO_SUBDIR.exists()) { params.add("--xml"); params.add(DEMO_XML_FILE.toString()); params.add("--classpath"); params.add(DEMO_SUBDIR.toString()); params.add("--model-pkg"); params.add("org.jsimpledb.demo"); final StringBuilder buf = new StringBuilder(); for (String param : params) buf.append(' ').append(param); System.err.println( this.getName() + ": automatically configuring demo database using the following flags:\n " + buf); } // Parse --classpath options prior to searching classpath for key/value implementations for (Iterator<String> i = params.iterator(); i.hasNext(); ) { final String param = i.next(); if (param.equals("-cp") || param.equals("--classpath")) { i.remove(); if (!i.hasNext()) this.usageError(); if (!this.appendClasspath(i.next())) return 1; i.remove(); } } // Parse (and remove) options supported by key/value implementations for (KVImplementation availableKVImplementation : KVImplementation.getImplementations()) { final Object config; try { config = availableKVImplementation.parseCommandLineOptions(params); } catch (IllegalArgumentException e) { System.err.println(this.getName() + ": " + (e.getMessage() != null ? e.getMessage() : e)); return 1; } if (config != null) this.kvConfigMap.put(availableKVImplementation, config); } // Parse options supported by this class final LinkedHashSet<String> modelPackages = new LinkedHashSet<>(); final LinkedHashSet<String> typePackages = new LinkedHashSet<>(); while (!params.isEmpty() && params.peekFirst().startsWith("-")) { final String option = params.removeFirst(); if (option.equals("-h") || option.equals("--help")) { this.usageMessage(); return 0; } else if (option.equals("-ro") || option.equals("--read-only")) this.readOnly = true; else if (option.equals("--verbose")) this.verbose = true; else if (option.equals("-v") || option.equals("--schema-version")) { if (params.isEmpty()) this.usageError(); final String vstring = params.removeFirst(); try { this.schemaVersion = Integer.parseInt(vstring); if (this.schemaVersion < 0) throw new IllegalArgumentException("schema version is negative"); } catch (Exception e) { System.err.println( this.getName() + ": invalid schema version `" + vstring + "': " + e.getMessage()); return 1; } } else if (option.equals("--model-pkg")) { if (params.isEmpty()) this.usageError(); modelPackages.add(params.removeFirst()); } else if (option.equals("--type-pkg")) { if (params.isEmpty()) this.usageError(); typePackages.add(params.removeFirst()); } else if (option.equals("-p") || option.equals("--pkg")) { if (params.isEmpty()) this.usageError(); final String packageName = params.removeFirst(); modelPackages.add(packageName); typePackages.add(packageName); } else if (option.equals("--new-schema")) this.allowNewSchema = true; else if (option.equals("--")) break; else if (!this.parseOption(option, params)) { System.err.println(this.getName() + ": unknown option `" + option + "'"); this.usageError(); return 1; } } // Decode what key/value implementations where specified and how they nest, if at all final Iterator<KVImplementation> i = this.kvConfigMap.keySet().iterator(); switch (this.kvConfigMap.size()) { case 0: System.err.println( this.getName() + ": no key/value database specified; use one of `--arraydb', etc."); this.usageError(); return 1; case 1: this.kvImplementation = i.next(); final Object config = this.kvConfigMap.get(this.kvImplementation); if (this.kvImplementation.requiresAtomicKVStore(config) || this.kvImplementation.requiresKVDatabase(config)) { System.err.println( this.getName() + ": " + this.kvImplementation.getDescription(config) + " requires the configuration of an underlying key/value technology; use one of `--arraydb', etc."); return 1; } break; case 2: // Put them in proper order: inner first, outer second final KVImplementation[] kvis = new KVImplementation[] {i.next(), i.next()}; final Object[] configs = new Object[] {this.kvConfigMap.get(kvis[0]), this.kvConfigMap.get(kvis[1])}; if (kvis[0].requiresAtomicKVStore(configs[0]) || kvis[0].requiresKVDatabase(configs[0])) { Collections.reverse(Arrays.asList(kvis)); Collections.reverse(Arrays.asList(configs)); } // Sanity check nesting requirements if ((kvis[0].requiresAtomicKVStore(configs[0]) || kvis[0].requiresKVDatabase(configs[0])) || !(kvis[1].requiresAtomicKVStore(configs[1]) || kvis[1].requiresKVDatabase(configs[1]))) { System.err.println( this.getName() + ": incompatible combination of " + kvis[0].getDescription(configs[0]) + " and " + kvis[1].getDescription(configs[1])); return 1; } // Nest them as required if (kvis[1].requiresAtomicKVStore(configs[1])) this.requiredAtomicKVStore = kvis[0]; else this.requiredKVDatabase = kvis[0]; this.kvImplementation = kvis[1]; break; default: System.err.println(this.getName() + ": too many key/value store(s) specified"); return 1; } // Scan for model and type classes final LinkedHashSet<String> emptyPackages = new LinkedHashSet<>(); emptyPackages.addAll(modelPackages); emptyPackages.addAll(typePackages); for (String packageName : modelPackages) { if (this.scanModelClasses(packageName) > 0) emptyPackages.remove(packageName); } for (String packageName : typePackages) { if (this.scanTypeClasses(packageName) > 0) emptyPackages.remove(packageName); } // Warn if we didn't find anything for (String packageName : emptyPackages) { final boolean isModel = modelPackages.contains(packageName); final boolean isType = typePackages.contains(packageName); if (isModel && isType) this.log.warn( "no Java model or custom FieldType classes found under package `" + packageName + "'"); else if (isModel) this.log.warn("no Java model classes found under package `" + packageName + "'"); else this.log.warn("no custom FieldType classes found under package `" + packageName + "'"); } // Done return -1; }