/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.shell.syntax; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.TreeSet; import org.jnode.nanoxml.XMLElement; import org.jnode.shell.CommandLine.Token; /** * An OptionSet syntax is like a Powerset syntax, except that: * <ol> * <li>the child syntaxes must all be OptionSyntaxes, and * <li>the syntax supports combining of flag options; e.g. "-ab" means "-a -b" * </ol> * * @author crawley@jnode.org */ public class OptionSetSyntax extends GroupSyntax { private static class FlagSetArgument extends Argument<Boolean> { private final List<OptionSyntax> flagOptions; private final ArgumentBundle bundle; FlagSetArgument(String label, ArgumentBundle bundle, List<OptionSyntax> flagOptions) { super(label, OPTIONAL + MULTIPLE, new Boolean[0], null); this.flagOptions = flagOptions; this.bundle = bundle; } @Override protected Boolean doAccept(Token token, int argumentFlags) throws CommandSyntaxException { String value = token.text; int len = value.length(); if (len < 2 || value.charAt(0) != '-') { throw new CommandSyntaxException("not a flag set"); } for (int i = 1; i < len; i++) { char ch = value.charAt(i); String shortOptName = "-" + ch; boolean found = false; for (OptionSyntax flagOption : flagOptions) { if (shortOptName.equals(flagOption.getShortOptName())) { bundle.getArgument(flagOption).accept(new Token(shortOptName), 0); found = true; break; } } if (!found) { throw new CommandSyntaxException("'" + ch + "' is not a known flag"); } } return Boolean.TRUE; } @Override protected String argumentKind() { return "flags"; } } private static Comparator<OptionSyntax> SHORT_NAME_ORDER = new Comparator<OptionSyntax>() { public int compare(OptionSyntax o1, OptionSyntax o2) { char c1 = o1.getShortOptName().charAt(1); char c2 = o2.getShortOptName().charAt(1); boolean l1 = Character.isLowerCase(c1); boolean l2 = Character.isLowerCase(c2); if (l1 == l2) { return (int) c2 - (int) c1; } else { return l1 ? -1 : +1; } } }; private final OptionSyntax[] optionSyntaxes; public OptionSetSyntax(String label, String description, OptionSyntax... syntaxes) { super(label, description, syntaxes); this.optionSyntaxes = syntaxes; } public OptionSetSyntax(String label, OptionSyntax... syntaxes) { this(label, null, syntaxes); } public OptionSetSyntax(OptionSyntax... syntaxes) { this(null, null, syntaxes); } @Override public MuSyntax prepare(ArgumentBundle bundle) { ArrayList<OptionSyntax> flagOptions = new ArrayList<OptionSyntax>(); MuSyntax[] childMuSyntaxes = new MuSyntax[optionSyntaxes.length]; for (int i = 0; i < optionSyntaxes.length; i++) { OptionSyntax childSyntax = optionSyntaxes[i]; Argument<?> arg = bundle.getArgument(childSyntax); if (arg instanceof FlagArgument && childSyntax.getShortOptName() != null) { flagOptions.add(childSyntax); } childMuSyntaxes[i] = childSyntax.prepare(bundle); } if (flagOptions.size() > 1) { // We deal with combined flag options by adding a proxy argument to the // bundle whose 'accept' method will unpick (say) '-ab', and dispatch // it to the corresponding FlagArguments. String label = MuSyntax.genLabel(); FlagSetArgument arg = new FlagSetArgument(label, bundle, flagOptions); bundle.addArgument(arg); childMuSyntaxes = new MuSyntax[] {new MuAlternation(childMuSyntaxes), new MuArgument(label)}; } String label = this.label == null ? MuSyntax.genLabel() : this.label; MuSyntax res = new MuAlternation(label, new MuSequence(new MuAlternation((String) null, childMuSyntaxes), new MuBackReference(label)), null); res.resolveBackReferences(); return res; } @Override public String format(ArgumentBundle bundle) { TreeSet<OptionSyntax> shortFlagOpts = new TreeSet<OptionSyntax>(SHORT_NAME_ORDER); for (int i = 0; i < optionSyntaxes.length; i++) { OptionSyntax optionSyntax = optionSyntaxes[i]; Argument<?> arg = bundle.getArgument(optionSyntax); if (arg instanceof FlagArgument && optionSyntax.getShortOptName() != null && optionSyntax.getLongOptName() == null) { shortFlagOpts.add(optionSyntax); } } StringBuilder sb = new StringBuilder(); if (!shortFlagOpts.isEmpty()) { sb.append("[ -"); for (OptionSyntax optionSyntax : shortFlagOpts) { sb.append(optionSyntax.getShortOptName().charAt(1)); } sb.append(" ]"); } for (int i = 0; i < optionSyntaxes.length; i++) { OptionSyntax optionSyntax = optionSyntaxes[i]; if (optionSyntax.getShortOptName() == null || !shortFlagOpts.contains(optionSyntax)) { if (sb.length() > 0) { sb.append(' '); } sb.append(optionSyntax.format(bundle)); } } return sb.toString(); } @Override public XMLElement toXML() { return basicElement("optionSet"); } }