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