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