    001    package org.maltparser.core.options;
    002
    003    import java.net.URL;
    004    import java.util.Collection;
    005    import java.util.HashMap;
    006    import java.util.HashSet;
    007    import java.util.Set;
    008    import java.util.TreeSet;
    009
    010    import javax.xml.parsers.DocumentBuilder;
    011    import javax.xml.parsers.DocumentBuilderFactory;
    012    import javax.xml.parsers.ParserConfigurationException;
    013
    014    import org.maltparser.core.exception.MaltChainedException;
    015    import org.maltparser.core.helper.SystemLogger;
    016    import org.maltparser.core.options.option.BoolOption;
    017    import org.maltparser.core.options.option.ClassOption;
    018    import org.maltparser.core.options.option.EnumOption;
    019    import org.maltparser.core.options.option.IntegerOption;
    020    import org.maltparser.core.options.option.Option;
    021    import org.maltparser.core.options.option.StringEnumOption;
    022    import org.maltparser.core.options.option.StringOption;
    023    import org.maltparser.core.options.option.UnaryOption;
    024    import org.w3c.dom.Element;
    025    import org.w3c.dom.NodeList;
    026    import org.xml.sax.SAXException;
    027
    028    /**
    029     * Organizes all the option descriptions. Option descriptions can be loaded from the application data <code>/appdata/options.xml</code>, but also
    030     * from a plugin option description file (always with the name <code>plugin.xml</code>).
    031     *
    032    * @author Johan Hall
    033    * @since 1.0
    034    **/
    035    public class OptionDescriptions {
    036    //      private static Logger logger = SystemLogger.logger();
    037            private HashMap<String, OptionGroup> optionGroups;
    038            private TreeSet<String> ambiguous;
    039            private HashMap<String, Option> unambiguousOptionMap;
    040            private HashMap<String, Option> ambiguousOptionMap;
    041            private HashMap<String, Option> flagOptionMap;
    042
    043            /**
    044             * Creates the Option Descriptions
    045             */
    046            public OptionDescriptions() {
    047                    optionGroups = new HashMap<String, OptionGroup>();
    048                    ambiguous = new TreeSet<String>();
    049                    unambiguousOptionMap = new HashMap<String, Option>();
    050                    ambiguousOptionMap = new HashMap<String, Option>();
    051                    flagOptionMap = new HashMap<String, Option>();
    052            }
    053
    054
    055            /**
    056             * Returns an option based on the option name and/or the option group name
    057             *
    058             * @param optiongroup   the name of the option group
    059             * @param optionname    the option name
    060             * @return an option based on the option name and/or the option group name
    061             * @throws MaltChainedException
    062             */
    063            public Option getOption(String optiongroup, String optionname) throws MaltChainedException {
    064                    if (optionname == null || optionname.length() <= 0) {
    065                            throw new OptionException("The option name '"+optionname+"' cannot be found" );
    066                    }
    067                    Option option;
    068                    if (ambiguous.contains(optionname.toLowerCase())) {
    069                            if (optiongroup == null || optiongroup.length() <= 0) {
    070                                    throw new OptionException("The option name '"+optionname+"' is ambiguous use option group name to distinguish the option. ");
    071                            }
    072                            else {
    073                                    option = ambiguousOptionMap.get(optiongroup.toLowerCase()+"-"+optionname.toLowerCase());
    074                                    if (option == null) {
    075                                            throw new OptionException("The option '--"+optiongroup.toLowerCase()+"-"+optionname.toLowerCase()+" does not exist. ");
    076                                    }
    077                            }
    078                    } else {
    079                            option = unambiguousOptionMap.get(optionname.toLowerCase());
    080                            if (option == null) {
    081                                    throw new OptionException("The option '--"+optionname.toLowerCase()+" doesn't exist. ");
    082                            }
    083                    }
    084                    return option;
    085            }
    086
    087            /**
    088             * Returns an option based on the option flag
    089             *
    090             * @param optionflag the option flag
    091             * @return an option based on the option flag
    092             * @throws MaltChainedException
    093             */
    094            public Option getOption(String optionflag) throws MaltChainedException {
    095                    Option option = flagOptionMap.get(optionflag);
    096                    if (option == null) {
    097                            throw new OptionException("The option flag -"+optionflag+" could not be found. ");
    098                    }
    099                    return option;
    100            }
    101
    102            /**
    103             * Returns a set of option that are marked as SAVEOPTION
    104             *
    105             * @return a set of option that are marked as SAVEOPTION
    106             */
    107            public Set<Option> getSaveOptionSet() {
    108                    Set<Option> optionToSave = new HashSet<Option>();
    109
    110                    for (String optionname : unambiguousOptionMap.keySet()) {
    111                            if (unambiguousOptionMap.get(optionname).getUsage() == Option.SAVE) {
    112                                    optionToSave.add(unambiguousOptionMap.get(optionname));
    113                            }
    114                    }
    115                    for (String optionname : ambiguousOptionMap.keySet()) {
    116                            if (ambiguousOptionMap.get(optionname).getUsage() == Option.SAVE) {
    117                                    optionToSave.add(ambiguousOptionMap.get(optionname));
    118                            }
    119                    }
    120                    return optionToSave;
    121            }
    122
    123            /**
    124             * Return a sorted set of option group names
    125             *
    126             * @return a sorted set of option group names
    127             */
    128            public TreeSet<String> getOptionGroupNameSet() {
    129                    return new TreeSet<String>(optionGroups.keySet());
    130            }
    131
    132            /**
    133             * Returns a collection of option that are member of an option group
    134             *
    135             * @param groupname the name of the option group
    136             * @return a collection of option that are member of an option group
    137             */
    138            public Collection<Option> getOptionGroupList(String groupname) {
    139                    return optionGroups.get(groupname).getOptionList();
    140            }
    141
    142            /**
    143             * Parse a XML file that contains the options used for controlling the application. The method
    144             * calls the parseOptionGroups to parse the set of option groups in the DOM tree.
    145             *
    146             * @param url   The path to a XML file that explains the options used in the application.
    147             * @throws OptionException
    148             */
    149            public void parseOptionDescriptionXMLfile(URL url) throws MaltChainedException {
    150                    if (url == null) {
    151                            throw new OptionException("The URL to the default option file is null. ");
    152                    }
    153
    154            try {
    155                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    156                DocumentBuilder db = dbf.newDocumentBuilder();
    157
    158                    Element root = db.parse(url.openStream()).getDocumentElement();
    159                    NodeList groups = root.getElementsByTagName("optiongroup");
    160                Element group;
    161                for (int i = 0; i < groups.getLength(); i++) {
    162                    group = (Element)groups.item(i);
    163                    String groupname = group.getAttribute("groupname").toLowerCase();
    164                    OptionGroup og = null;
    165                    if (optionGroups.containsKey(groupname)) {
    166                            og = optionGroups.get(groupname);
    167                    } else {
    168                            optionGroups.put(groupname, new OptionGroup(groupname));
    169                            og = optionGroups.get(groupname);
    170                    }
    171                    parseOptionsDescription(group, og);
    172                }
    173            } catch (java.io.IOException e) {
    174                    throw new OptionException("Can't find the file "+url.toString()+".", e);
    175            } catch (OptionException e) {
    176                    throw new OptionException("Problem parsing the file "+url.toString()+". ", e);
    177            } catch (ParserConfigurationException e) {
    178                    throw new OptionException("Problem parsing the file "+url.toString()+". ", e);
    179            } catch (SAXException e) {
    180                    throw new OptionException("Problem parsing the file "+url.toString()+". ", e);
    181            }
    182            }
    183
    184
    185            /**
    186             * Parse a set of options within an option group to collect all information of individual options.
    187             *
    188             * @param group a reference to an individual option group in the DOM tree.
    189             * @param og a reference to the corresponding option group in the HashMap.
    190             * @throws OptionException
    191             */
    192            private void parseOptionsDescription(Element group, OptionGroup og) throws MaltChainedException {
    193                    NodeList options = group.getElementsByTagName("option");
    194            Element option;
    195            for (int i = 0; i < options.getLength(); i++) {
    196                    option = (Element)options.item(i);
    197                    String optionname = option.getAttribute("name").toLowerCase();
    198                    String optiontype = option.getAttribute("type").toLowerCase();
    199                    String defaultValue = option.getAttribute("default");
    200                    String usage = option.getAttribute("usage").toLowerCase();
    201                    String flag = option.getAttribute("flag");
    202
    203                    NodeList shortdescs = option.getElementsByTagName("shortdesc");
    204                    Element shortdesc;
    205                    String shortdesctext = "";
    206                    if (shortdescs.getLength() == 1) {
    207                            shortdesc = (Element)shortdescs.item(0);
    208                            shortdesctext = shortdesc.getTextContent();
    209                    }
    210
    211                    if (optiontype.equals("string") || optiontype.equals("bool") || optiontype.equals("integer") || optiontype.equals("unary")) {
    212                            Option op = og.getOption(optionname);
    213                            if (op != null) {
    214                                    throw new OptionException("The option name '"+optionname+"' for option group '"+og.getName()+"' already exists. It is only allowed to override the class and enum option type to add legal value. ");
    215                            }
    216                    } else if (optiontype.equals("class") || optiontype.equals("enum") || optiontype.equals("stringenum")) {
    217                            Option op = og.getOption(optionname);
    218                            if (op != null) {
    219                                    if (op instanceof EnumOption && !optiontype.equals("enum")) {
    220                                            throw new OptionException("The option name '"+optionname+"' for option group '"+og.getName()+"' already exists. The existing option is of enum type, but the new option is of '"+optiontype+"' type. ");
    221                                    }
    222                                    if (op instanceof ClassOption && !optiontype.equals("class")) {
    223                                            throw new OptionException("The option name '"+optionname+"' for option group '"+og.getName()+"' already exists. The existing option is of class type, but the new option is of '"+optiontype+"' type. ");
    224                                    }
    225                                    if (op instanceof StringEnumOption && !optiontype.equals("stringenum")) {
    226                                            throw new OptionException("The option name '"+optionname+"' for option group '"+og.getName()+"' already exists. The existing option is of urlenum type, but the new option is of '"+optiontype+"' type. ");
    227                                    }
    228                            }
    229                    }
    230                    if (optiontype.equals("string")) {
    231                            og.addOption(new StringOption(og, optionname, shortdesctext, flag, usage, defaultValue));
    232                    } else if (optiontype.equals("bool")) {
    233                            og.addOption(new BoolOption(og, optionname, shortdesctext, flag, usage, defaultValue));
    234                    } else if (optiontype.equals("integer")) {
    235                            og.addOption(new IntegerOption(og, optionname, shortdesctext, flag, usage, defaultValue));
    236                    } else if (optiontype.equals("unary")) {
    237                            og.addOption(new UnaryOption(og, optionname, shortdesctext, flag, usage));
    238                    } else if (optiontype.equals("enum")) {
    239                            Option op = og.getOption(optionname);
    240                            EnumOption eop = null;
    241                            if (op == null) {
    242                                    eop = new EnumOption(og, optionname, shortdesctext, flag, usage);
    243                            } else {
    244                                    if (op instanceof EnumOption) {
    245                                            eop = (EnumOption)op;
    246                                    }
    247                            }
    248
    249                            NodeList legalvalues = option.getElementsByTagName("legalvalue");
    250                            Element legalvalue;
    251                            for (int j = 0; j < legalvalues.getLength(); j++) {
    252                                    legalvalue = (Element)legalvalues.item(j);
    253                                    String legalvaluename = legalvalue.getAttribute("name");
    254                                    String legalvaluetext = legalvalue.getTextContent();
    255                                    eop.addLegalValue(legalvaluename, legalvaluetext);
    256                            }
    257                            if (op == null) {
    258                                    eop.setDefaultValue(defaultValue);
    259                                    og.addOption(eop);
    260                            }
    261
    262                    } else if (optiontype.equals("class") ) {
    263                            Option op = og.getOption(optionname);
    264                            ClassOption cop = null;
    265                            if (op == null) {
    266                                    cop = new ClassOption(og, optionname, shortdesctext, flag, usage);
    267                            } else {
    268                                    if (op instanceof ClassOption) {
    269                                            cop = (ClassOption)op;
    270                                    }
    271                            }
    272
    273                            NodeList legalvalues = option.getElementsByTagName("legalvalue");
    274                            Element legalvalue;
    275                            for (int j = 0; j < legalvalues.getLength(); j++) {
    276                                    legalvalue = (Element)legalvalues.item(j);
    277                                    String legalvaluename = legalvalue.getAttribute("name").toLowerCase();
    278                                    String classname = legalvalue.getAttribute("class");
    279                                    String legalvaluetext = legalvalue.getTextContent();
    280                                    cop.addLegalValue(legalvaluename, legalvaluetext, classname);
    281                            }
    282                            if (op == null) {
    283                                    cop.setDefaultValue(defaultValue);
    284                                    og.addOption(cop);
    285                            }
    286                    } else if (optiontype.equals("stringenum") ) {
    287                            Option op = og.getOption(optionname);
    288                            StringEnumOption ueop = null;
    289                            if (op == null) {
    290                                    ueop = new StringEnumOption(og, optionname, shortdesctext, flag, usage);
    291                            } else {
    292                                    if (op instanceof StringEnumOption) {
    293                                            ueop = (StringEnumOption)op;
    294                                    }
    295                            }
    296
    297                            NodeList legalvalues = option.getElementsByTagName("legalvalue");
    298                            Element legalvalue;
    299                            for (int j = 0; j < legalvalues.getLength(); j++) {
    300                                    legalvalue = (Element)legalvalues.item(j);
    301                                    String legalvaluename = legalvalue.getAttribute("name").toLowerCase();
    302                                    String url = legalvalue.getAttribute("mapto");
    303                                    String legalvaluetext = legalvalue.getTextContent();
    304                                    ueop.addLegalValue(legalvaluename, legalvaluetext, url);
    305                            }
    306                            if (op == null) {
    307                                    ueop.setDefaultValue(defaultValue);
    308                                    og.addOption(ueop);
    309                            }
    310                    } else {
    311                            throw new OptionException("Illegal option type found in the setting file. ");
    312                    }
    313            }
    314            }
    315
    316            /**
    317             * Creates several option maps for fast access to individual options.
    318             *
    319             * @throws OptionException
    320             */
    321            public void generateMaps() throws MaltChainedException {
    322            for (String groupname : optionGroups.keySet()) {
    323                    OptionGroup og = optionGroups.get(groupname);
    324                    Collection<Option> options = og.getOptionList();
    325
    326                    for (Option option : options) {
    327                            if (ambiguous.contains(option.getName())) {
    328                                    option.setAmbiguous(true);
    329                                    ambiguousOptionMap.put(option.getGroup().getName()+"-"+option.getName(), option);
    330                            } else {
    331                                    if (!unambiguousOptionMap.containsKey(option.getName())) {
    332                                            unambiguousOptionMap.put(option.getName(), option);
    333                                    } else {
    334                                            Option ambig = unambiguousOptionMap.get(option.getName());
    335                                            unambiguousOptionMap.remove(ambig);
    336                                            ambig.setAmbiguous(true);
    337                                            option.setAmbiguous(true);
    338                                            ambiguous.add(option.getName());
    339                                            ambiguousOptionMap.put(ambig.getGroup().getName()+"-"+ambig.getName(), ambig);
    340                                            ambiguousOptionMap.put(option.getGroup().getName()+"-"+option.getName(), option);
    341                                    }
    342                            }
    343                            if (option.getFlag() != null) {
    344                                    Option co = flagOptionMap.get(option.getFlag());
    345                                    if (co != null) {
    346                                            flagOptionMap.remove(co);
    347                                            co.setFlag(null);
    348                                            option.setFlag(null);
    349                                            if (SystemLogger.logger().isDebugEnabled()) {
    350                                                    SystemLogger.logger().debug("Ambiguous use of an option flag -> the option flag is removed for all ambiguous options\n");
    351                                            }
    352                                    } else {
    353                                            flagOptionMap.put(option.getFlag(), option);
    354                                    }
    355                            }
    356                    }
    357            }
    358            }
    359
    360            /**
    361             * Returns a string representation that contains printable information of several options maps
    362             *
    363             * @return a string representation that contains printable information of several options maps
    364             */
    365            public String toStringMaps() {
    366                    final StringBuilder sb = new StringBuilder();
    367                    sb.append("UnambiguousOptionMap\n");
    368            for (String optionname : new TreeSet<String>(unambiguousOptionMap.keySet())) {
    369                    sb.append("   "+optionname+"\n");
    370            }
    371            sb.append("AmbiguousSet\n");
    372            for (String optionname : ambiguous) {
    373                    sb.append("   "+optionname+"\n");
    374            }
    375            sb.append("AmbiguousOptionMap\n");
    376            for (String optionname : new TreeSet<String>(ambiguousOptionMap.keySet())) {
    377                    sb.append("   "+optionname+"\n");
    378            }
    379            sb.append("CharacterOptionMap\n");
    380            for (String flag : new TreeSet<String>(flagOptionMap.keySet())) {
    381                    sb.append("   -"+flag+" -> "+flagOptionMap.get(flag).getName()+"\n");
    382            }
    383                    return sb.toString();
    384            }
    385
    386            /**
    387             * Returns a string representation of a option group without the option group name in the string.
    388             *
    389             * @param groupname     The option group name
    390             * @return a string representation of a option group
    391             */
    392            public String toStringOptionGroup(String groupname) {
    393                    OptionGroup.toStringSetting = OptionGroup.NOGROUPNAME;
    394                    return optionGroups.get(groupname).toString()+"\n";
    395            }
    396
    397            /* (non-Javadoc)
    398             * @see java.lang.Object#toString()
    399             */
    400            public String toString() {
    401                    OptionGroup.toStringSetting = OptionGroup.WITHGROUPNAME;
    402                    final StringBuilder sb = new StringBuilder();
    403            for (String groupname : new TreeSet<String>(optionGroups.keySet())) {
    404                    sb.append(optionGroups.get(groupname).toString()+"\n");
    405            }
    406                    return sb.toString();
    407            }
    408    }