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 }