001package org.maltparser.core.options;
002
003import java.io.BufferedReader;
004import java.io.BufferedWriter;
005import java.io.File;
006import java.io.FileInputStream;
007import java.io.FileNotFoundException;
008import java.io.FileOutputStream;
009import java.io.IOException;
010import java.io.InputStreamReader;
011import java.io.OutputStreamWriter;
012import java.io.UnsupportedEncodingException;
013import java.net.URL;
014
015import java.util.Formatter;
016import java.util.HashMap;
017import java.util.Set;
018import java.util.regex.Pattern;
019
020import javax.xml.parsers.DocumentBuilder;
021import javax.xml.parsers.DocumentBuilderFactory;
022import javax.xml.parsers.ParserConfigurationException;
023
024import org.maltparser.core.exception.MaltChainedException;
025import org.maltparser.core.options.option.EnumOption;
026import org.maltparser.core.options.option.ClassOption;
027import org.maltparser.core.options.option.StringEnumOption;
028import org.maltparser.core.options.option.Option;
029import org.maltparser.core.options.option.UnaryOption;
030import org.maltparser.core.plugin.PluginLoader;
031import org.w3c.dom.Element;
032import org.w3c.dom.NodeList;
033import org.xml.sax.SAXException;
034
035
036/**
037 *  Option Manager is the management class for all option handling. All queries and manipulations of an option or an option value
038 *   should go through this class. 
039 *  
040 * @author Johan Hall
041 * @since 1.0
042**/
043public class OptionManager {
044        public static final int DEFAULTVALUE = -1;
045        private final OptionDescriptions optionDescriptions;
046        private final OptionValues optionValues;
047        private static OptionManager uniqueInstance = new OptionManager();
048        
049        /**
050         * Creates the Option Manager
051         */
052        private OptionManager() {
053                optionValues = new OptionValues();
054                optionDescriptions = new OptionDescriptions();
055        }
056        
057        /**
058        * Returns a reference to the single instance.
059        */
060        public static OptionManager instance() {
061                return uniqueInstance;
062        }
063        
064        /**
065         * Loads the option description file <code>/appdata/options.xml</code>
066         * 
067         * @throws MaltChainedException
068         */
069        public void loadOptionDescriptionFile() throws MaltChainedException {
070                optionDescriptions.parseOptionDescriptionXMLfile(getClass().getResource("/appdata/options.xml"));
071        }
072        
073
074        /**
075         * Loads the option description file
076         * 
077         * @param url   URL of the option description file
078         * @throws MaltChainedException
079         */
080        public void loadOptionDescriptionFile(URL url) throws MaltChainedException {
081                optionDescriptions.parseOptionDescriptionXMLfile(url);
082        }
083        
084        /**
085         * Returns the option description 
086         * 
087         * @return the option description 
088         */
089        public OptionDescriptions getOptionDescriptions() {
090                return optionDescriptions;
091        }
092        
093        public boolean hasOptions() {
094                return optionDescriptions.hasOptions();
095        }
096        
097        /**
098         * Returns the option value for an option that is specified by the option group name and option name. The
099         * container name points out the specific option container. 
100         * 
101         * 
102         * @param containerIndex        The index of the option container (0..n and -1 is default values). 
103         * @param optiongroup   The name of the option group.
104         * @param optionname    The name of the option.
105         * @return an object that contains the value of the option, <i>null</i> if the option value could not be found.
106         * @throws OptionException
107         */
108        public Object getOptionValue(int containerIndex, String optiongroup, String optionname) throws MaltChainedException {
109                Option option = optionDescriptions.getOption(optiongroup, optionname);
110
111                if (containerIndex == OptionManager.DEFAULTVALUE) {
112                        return option.getDefaultValueObject();
113                } 
114                Object value = optionValues.getOptionValue(containerIndex, option);
115                if (value == null) {
116                        value = option.getDefaultValueObject();
117                }
118                return value;
119        }
120        
121        public Object getOptionDefaultValue(String optiongroup, String optionname) throws MaltChainedException {
122                Option option = optionDescriptions.getOption(optiongroup, optionname);
123                return option.getDefaultValueObject();
124        }
125        
126        public Object getOptionValueNoDefault(int containerIndex, String optiongroup, String optionname) throws MaltChainedException {
127                Option option = optionDescriptions.getOption(optiongroup, optionname);
128
129                if (containerIndex == OptionManager.DEFAULTVALUE) {
130                        return option.getDefaultValueObject();
131                } 
132                return optionValues.getOptionValue(containerIndex, option);
133        }
134        
135        /**
136         * Returns a string representation of the option value for an option that is specified by the option group name and the option name. The
137         * container name points out the specific option container. 
138         * 
139         * @param containerIndex        The index of the option container (0..n and -1 is default values). 
140         * @param optiongroup   The name of the option group.
141         * @param optionname    The name of the option.
142         * @return a string representation of the option value
143         * @throws MaltChainedException
144         */
145        public String getOptionValueString(int containerIndex, String optiongroup, String optionname) throws MaltChainedException {
146                Option option = optionDescriptions.getOption(optiongroup, optionname);
147                String value = optionValues.getOptionValueString(containerIndex, option);
148                if (value == null) {
149                        value = option.getDefaultValueString();
150                }
151                return value;
152        }
153        
154        public String getOptionValueStringNoDefault(int containerIndex, String optiongroup, String optionname) throws MaltChainedException {
155                return optionValues.getOptionValueString(containerIndex, optionDescriptions.getOption(optiongroup, optionname));
156        }
157        
158        public void addLegalValue(String optiongroup, String optionname, String value, String desc, String target) throws MaltChainedException {
159                Option option = optionDescriptions.getOption(optiongroup, optionname);
160                if (option != null) {
161                        if (option instanceof EnumOption) {
162                                ((EnumOption)option).addLegalValue(value, desc);
163                        } else if (option instanceof ClassOption) {
164                                ((ClassOption)option).addLegalValue(value, desc, target);
165                        } else if (option instanceof StringEnumOption) {
166                                ((StringEnumOption)option).addLegalValue(value, desc, target);
167                        }
168                }
169        }
170        
171        /**
172         * Overloads the option value specified by the container index, the option group name, the option name.
173         * This method is used to override option that have specific dependencies. 
174         * 
175         * @param containerIndex        the index of the option container (0..n and -1 is default values). 
176         * @param optiongroup   the name of the option group.
177         * @param optionname    the name of the option.
178         * @param value the option value that should replace the current option value.
179         * @throws MaltChainedException
180         */
181        public void overloadOptionValue(int containerIndex, String optiongroup, String optionname, String value) throws MaltChainedException {
182//              Option option = optionDescriptions.getOption(optiongroup, optionname);
183//              if (value == null) {
184//              throw new OptionException("The option value is missing. ");
185//      }
186//      Object ovalue = option.getValueObject(value);
187//      optionValues.addOptionValue(OptionContainer.DEPENDENCIES_RESOLVED, containerIndex, option, ovalue);
188                overloadOptionValue(containerIndex, OptionContainer.DEPENDENCIES_RESOLVED, optiongroup, optionname, value);
189        }
190        
191        public void overloadOptionValue(int containerIndex, int containerType, String optiongroup, String optionname, String value) throws MaltChainedException {
192                Option option = optionDescriptions.getOption(optiongroup, optionname);
193                if (value == null) {
194                throw new OptionException("The option value is missing. ");
195        }
196        Object ovalue = option.getValueObject(value);
197        optionValues.addOptionValue(containerType, containerIndex, option, ovalue);
198        }
199        
200        /**
201         * Returns the number of option values for a particular option container.
202         * 
203         * @param containerIndex        The index of the option container (0..n). 
204         * @return the number of option values for a particular option container.
205         */
206        public int getNumberOfOptionValues(int containerIndex) {
207                return optionValues.getNumberOfOptionValues(containerIndex);
208        }
209        
210        /**
211         * Returns a sorted set of container names.
212         * 
213         * @return      a sorted set of container names.
214         */
215        public Set<Integer> getOptionContainerIndices() {
216                return optionValues.getOptionContainerIndices();
217        }
218        /**
219         * Loads the saved options (options that are marked with <code>usage=save</code>). 
220         * 
221         * @param fileName      The path to the file where to load the saved options.
222         * @throws MaltChainedException
223         */
224        public void loadOptions(int containerIndex, String fileName) throws MaltChainedException { 
225                try {
226                        loadOptions(containerIndex, new InputStreamReader(new FileInputStream(fileName), "UTF-8"));
227                } catch (FileNotFoundException e) {
228                        throw new OptionException("The saved option file '"+fileName+"' cannot be found. ", e);
229                } catch (UnsupportedEncodingException e) {
230                        throw new OptionException("The charset is unsupported. ", e);
231                }
232        }
233        
234
235        /**
236         * Loads the saved options (options that are marked with <code>usage=Option.SAVE</code>). 
237         * 
238         * @param isr   the input stream reader of the saved options file.
239         * @throws MaltChainedException
240         */
241        public void loadOptions(int containerIndex, InputStreamReader isr) throws MaltChainedException { 
242                try {
243                        BufferedReader br = new BufferedReader(isr);
244                        String line = null;
245                        Option option = null;
246                        Pattern tabPattern = Pattern.compile("\t");
247                        while ((line = br.readLine()) != null) {
248                                String[] items = tabPattern.split(line);
249                                if (items.length < 3 || items.length > 4) {
250                                        throw new OptionException("Could not load the saved option. ");
251                                }
252                                option = optionDescriptions.getOption(items[1], items[2]);
253                                Object ovalue;
254                                if (items.length == 3) {
255                                        ovalue = new String("");
256                                } else {
257                                        if (option instanceof ClassOption) {
258                                                if (items[3].startsWith("class ")) {
259                                                        Class<?> clazz = null;
260                                                        if (PluginLoader.instance() != null) {
261                                                                clazz = PluginLoader.instance().getClass(items[3].substring(6));
262                                                        }
263                                                        if (clazz == null) {
264                                                                clazz = Class.forName(items[3].substring(6));
265                                                        }
266                                                        ovalue = option.getValueObject(((ClassOption)option).getLegalValueString(clazz));
267                                                } else {
268                                                        ovalue = option.getValueObject(items[3]);
269                                                }
270                                        } else {
271                                                ovalue = option.getValueObject(items[3]);
272                                        }
273                                }
274                                optionValues.addOptionValue(OptionContainer.SAVEDOPTION, containerIndex, option, ovalue);
275                        }
276
277                        br.close();
278                } catch (ClassNotFoundException e) {
279                        throw new OptionException("The class cannot be found. ", e);
280                } catch (NumberFormatException e) {
281                        throw new OptionException("Option container index isn't an integer value. ", e);
282                } catch (IOException e) {
283                        throw new OptionException("Error when reading the saved options. ", e);
284                }
285        }
286        
287        /**
288         * Saves all options that are marked as <code>usage=Option.SAVE</code>
289         * 
290         * @param fileName      The path to the file where the saveOption should by saved.
291         */
292        public void saveOptions(String fileName) throws MaltChainedException { 
293                try {
294                        saveOptions(new OutputStreamWriter(new FileOutputStream(fileName), "UTF-8"));
295                } catch (FileNotFoundException e) {
296                        throw new OptionException("The file '"+fileName+"' cannot be created. ", e);
297                } catch (UnsupportedEncodingException e) {
298                        throw new OptionException("The charset 'UTF-8' is unsupported. ", e);
299                }
300                
301        }
302        
303        /**
304         * Saves all options that are marked as <code>usage=Option.SAVE</code>
305         * 
306         * @param osw   the output stream writer of the saved option file
307         * @throws MaltChainedException
308         */
309        public void saveOptions(OutputStreamWriter osw) throws MaltChainedException { 
310                try {
311                        BufferedWriter bw = new BufferedWriter(osw);
312                        Set<Option> optionToSave = optionDescriptions.getSaveOptionSet();
313                        
314                        Object value = null;
315                        for (Integer index : optionValues.getOptionContainerIndices()) {
316                                for (Option option : optionToSave) {
317                                        value = optionValues.getOptionValue(index, option);
318                                        if (value == null) {
319                                                value = option.getDefaultValueObject();
320                                        }
321                                        bw.append(index+"\t"+option.getGroup().getName()+"\t"+option.getName()+"\t"+value+"\n");
322                                }
323                        }
324                        bw.flush();
325                        bw.close();
326                } catch (IOException e) {
327                        throw new OptionException("Error when saving the saved options. ", e);
328                } 
329        }
330        
331        /**
332         * Saves all options that are marked as usage=Option.SAVE for a particular option container.
333         * 
334         * @param containerIndex        The index of the option container (0..n). 
335         * @param fileName      The path to the file where the saveOption should by saved.
336         */
337        public void saveOptions(int containerIndex, String fileName) throws MaltChainedException { 
338                try {
339                        saveOptions(containerIndex, new OutputStreamWriter(new FileOutputStream(fileName), "UTF-8"));
340                } catch (FileNotFoundException e) {
341                        throw new OptionException("The file '"+fileName+"' cannot be found.", e);
342                } catch (UnsupportedEncodingException e) {
343                        throw new OptionException("The charset 'UTF-8' is unsupported. ", e);
344                }
345        }
346
347        /**
348         * Saves all options that are marked as usage=Option.SAVE for a particular option container.
349         * 
350         * @param containerIndex The index of the option container (0..n). 
351         * @param osw   the output stream writer of the saved option file
352         * @throws MaltChainedException
353         */
354        public void saveOptions(int containerIndex, OutputStreamWriter osw) throws MaltChainedException { 
355                try {
356                        BufferedWriter bw = new BufferedWriter(osw);
357                        Set<Option> optionToSave = optionDescriptions.getSaveOptionSet();
358                        
359                        Object value = null;
360                        for (Option option : optionToSave) {
361                                value = optionValues.getOptionValue(containerIndex, option);
362                                if (value == null) {
363                                        value = option.getDefaultValueObject();
364                                }
365                                bw.append(containerIndex+"\t"+option.getGroup().getName()+"\t"+option.getName()+"\t"+value+"\n");
366                        }
367
368                        bw.flush();
369                        bw.close();
370                } catch (IOException e) {
371                        throw new OptionException("Error when saving the saved options.", e);
372                } 
373        }
374
375        /**
376         * Creates several option maps for fast access to individual options.  
377         * 
378         * @throws OptionException
379         */
380        public void generateMaps() throws MaltChainedException {
381                optionDescriptions.generateMaps();
382        }
383        
384        public boolean parseCommandLine(String argString, int containerIndex) throws MaltChainedException {
385                return parseCommandLine(argString.split(" "), containerIndex);
386        }
387        
388        /**
389         * Parses the command line arguments.
390         * 
391         * @param args An array of arguments that are supplied when starting the application. 
392         * @throws OptionException
393         */
394        public boolean parseCommandLine(String[] args, int containerIndex) throws MaltChainedException {
395                if (args == null  || args.length == 0) {
396                        return false;
397                }
398                int i = 0;
399                HashMap<String,String> oldFlags = new HashMap<String,String>();
400                oldFlags.put("llo", "lo"); oldFlags.put("lso", "lo"); 
401                oldFlags.put("lli", "li"); oldFlags.put("lsi", "li");
402                oldFlags.put("llx", "lx"); oldFlags.put("lsx", "lx");
403                oldFlags.put("llv", "lv"); oldFlags.put("lsv", "lv");
404                while (i < args.length) {
405                        Option option = null;
406                        String value = null;
407                        /* Recognizes
408                         * --optiongroup-optionname=value
409                         * --optionname=value
410                         * --optiongroup-optionname (unary option)
411                         * --optionname (unary option)
412                         */ 
413                        if (args[i].startsWith("--")) {
414                                if (args[i].length() == 2) {
415                                        throw new OptionException("The argument contains only '--', please check the user guide to see the correct format. ");
416                                }
417                                String optionstring;
418                                String optiongroup;
419                                String optionname;
420                                int indexEqualSign = args[i].indexOf('=');
421                                if (indexEqualSign != -1) {
422                                        value = args[i].substring(indexEqualSign+1);
423                                        optionstring = args[i].substring(2, indexEqualSign);
424                                } else {
425                                        value = null;
426                                        optionstring = args[i].substring(2);
427                                }
428                                int indexMinusSign = optionstring.indexOf('-');
429                                if (indexMinusSign != -1) {
430                                        optionname = optionstring.substring(indexMinusSign+1);
431                                        optiongroup = optionstring.substring(0, indexMinusSign);                                
432                                } else {
433                                        optiongroup = null;
434                                        optionname = optionstring;
435                                }
436                                
437                                option = optionDescriptions.getOption(optiongroup, optionname);
438                                if (option instanceof UnaryOption) {
439                                        value = "used";
440                                }
441                                i++;
442                        } 
443                        /* Recognizes
444                         * -optionflag value
445                         * -optionflag (unary option)
446                         */
447                        else if (args[i].startsWith("-")) {
448                                if (args[i].length() < 2) {
449                                        throw new OptionException("Wrong use of option flag '"+args[i]+"', please check the user guide to see the correct format. ");
450                                }
451                                String flag = "";
452                                if (oldFlags.containsKey(args[i].substring(1))) {
453                                        flag = oldFlags.get(args[i].substring(1));
454                                } else {
455                                        flag = args[i].substring(1);
456                                }
457                                
458                                // Error message if the old flag '-r' (root handling) is used 
459                                if (args[i].substring(1).equals("r")) {
460                                        throw new OptionException("The flag -r (root_handling) is replaced with two flags -nr (allow_root) and -ne (allow_reduce) since MaltParser 1.7. Read more about these changes in the user guide.");
461                                }
462                                
463                            option = optionDescriptions.getOption(flag);
464
465                                if (option instanceof UnaryOption) {
466                                        value = "used";
467                                } else {
468                                        i++;
469                                        if (args.length > i) {
470                                                value = args[i];
471                                        } else {
472                                                throw new OptionException("Could not find the corresponding value for -"+option.getFlag()+". ");
473                                        }
474                                }
475                                i++;
476                        } else {
477                                throw new OptionException("The option should starts with a minus sign (-), error at argument '"+args[i]+"'");
478                        }
479                        Object optionvalue = option.getValueObject(value);
480                        optionValues.addOptionValue(OptionContainer.COMMANDLINE, containerIndex, option, optionvalue);
481                }
482                return true;
483        }
484        
485        
486        /**
487         * Parses the option file for option values. 
488         * 
489         * @param fileName The option file name (must be a xml file).
490         * @throws OptionException
491         */
492        public void parseOptionInstanceXMLfile(String fileName) throws MaltChainedException {
493                File file = new File(fileName);
494                
495        try {
496            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
497            DocumentBuilder db = dbf.newDocumentBuilder();
498            
499                Element root = db.parse(file).getDocumentElement();
500                NodeList containers = root.getElementsByTagName("optioncontainer");
501            Element container;
502            for (int i = 0; i < containers.getLength(); i++) {
503                container = (Element)containers.item(i);
504                parseOptionValues(container, i);
505            }   
506        } catch (IOException e) {
507                throw new OptionException("Can't find the file "+fileName+". ", e);
508        } catch (OptionException e) {
509                throw new OptionException("Problem parsing the file "+fileName+". ", e);
510        } catch (ParserConfigurationException e) {
511                throw new OptionException("Problem parsing the file "+fileName+". ", e);
512        } catch (SAXException e) {
513                throw new OptionException("Problem parsing the file "+fileName+". ", e);
514        }
515        }
516        
517        /**
518         * Parses an option container for option values.
519         * 
520         * @param container     a reference to an individual option container in the DOM tree.
521         * @param containerName the name of this container.
522         * @throws OptionException
523         */
524        private void parseOptionValues(Element container, int containerIndex) throws MaltChainedException {
525                NodeList optiongroups = container.getElementsByTagName("optiongroup");
526        Element optiongroup;
527        for (int i = 0; i < optiongroups.getLength(); i++) {
528                optiongroup = (Element)optiongroups.item(i);
529                String groupname = optiongroup.getAttribute("groupname").toLowerCase();
530                if (groupname == null) {
531                        throw new OptionException("The option group name is missing. ");
532                }
533                NodeList optionvalues = optiongroup.getElementsByTagName("option");
534            Element optionvalue;
535            
536            for (int j = 0; j < optionvalues.getLength(); j++) {
537                optionvalue = (Element)optionvalues.item(j); 
538                String optionname = optionvalue.getAttribute("name").toLowerCase();
539                String value = optionvalue.getAttribute("value");
540                
541                if (optionname == null) {
542                        throw new OptionException("The option name is missing. ");
543                }
544
545                Option option = optionDescriptions.getOption(groupname, optionname);
546
547                if (option instanceof UnaryOption) {
548                                        value = "used";
549                                }
550                if (value == null) {
551                        throw new OptionException("The option value is missing. ");
552                }
553                Object ovalue = option.getValueObject(value);
554                optionValues.addOptionValue(OptionContainer.OPTIONFILE, containerIndex, option, ovalue);
555            }
556        }
557        }
558        
559        /**
560         * Returns a string representation of all option value, except the options in a option group specified
561         * by the excludeGroup argument.
562         * 
563         * @param containerIndex The index of the option container (0..n and -1 is default values). 
564         * @param excludeGroups a set of option group names that should by excluded in the string representation
565         * @return a string representation of all option value
566         * @throws MaltChainedException
567         */
568        public String toStringPrettyValues(int containerIndex, Set<String> excludeGroups) throws MaltChainedException {
569                int reservedSpaceForOptionName = 30;
570                OptionGroup.toStringSetting = OptionGroup.WITHGROUPNAME;
571                StringBuilder sb = new StringBuilder();
572                if (containerIndex == OptionManager.DEFAULTVALUE) {
573                        for (String groupname : optionDescriptions.getOptionGroupNameSet()) {
574                                if (excludeGroups.contains(groupname)) continue;
575                                sb.append(groupname+"\n");
576                                for (Option option : optionDescriptions.getOptionGroupList(groupname)) {
577                                        int nSpaces = reservedSpaceForOptionName - option.getName().length();
578                                        if (nSpaces <= 1) {
579                                                nSpaces = 1;
580                                        }
581                                        sb.append(new Formatter().format("  %s (%4s)%"+nSpaces+"s %s\n", option.getName(), "-"+option.getFlag()," ", option.getDefaultValueString()));
582                                }
583                        }
584                } else {
585                        for (String groupname : optionDescriptions.getOptionGroupNameSet()) {
586                                if (excludeGroups.contains(groupname)) continue;
587                                sb.append(groupname+"\n");
588                                for (Option option : optionDescriptions.getOptionGroupList(groupname)) {
589                                        String value = optionValues.getOptionValueString(containerIndex, option);
590                                        int nSpaces = reservedSpaceForOptionName - option.getName().length();
591                                        if (nSpaces <= 1) {
592                                                nSpaces = 1;
593                                        }
594                                        
595                                        if (value == null) {
596                                                sb.append(new Formatter().format("  %s (%4s)%"+nSpaces+"s %s\n", option.getName(), "-"+option.getFlag(), " ", option.getDefaultValueString()));
597                                        } else {
598                                                sb.append(new Formatter().format("  %s (%4s)%"+nSpaces+"s %s\n", option.getName(), "-"+option.getFlag(), " ", value));
599                                        }
600                                }
601                        }
602                }
603                return sb.toString();
604        }
605        
606        /* (non-Javadoc)
607         * @see java.lang.Object#toString()
608         */
609        public String toString() {
610                StringBuilder sb = new StringBuilder();
611                sb.append(optionDescriptions+"\n");
612                sb.append(optionValues+"\n");
613                return sb.toString();
614        }
615}