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