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