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