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