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    }