package org.maltparser.core.options;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.maltparser.core.exception.MaltChainedException;
import org.maltparser.core.helper.HashSet;
import org.maltparser.core.helper.SystemLogger;
import org.maltparser.core.options.option.*;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Organizes all the option descriptions. Option descriptions can be loaded from
* the application data
* <code>/appdata/options.xml</code>, but also from a plugin option description
* file (always with the name
* <code>plugin.xml</code>).
*
* @author Johan Hall
* @since 1.0
*
*/
public class OptionDescriptions {
// private static Logger logger = SystemLogger.logger();
private HashMap<String, OptionGroup> optionGroups;
private TreeSet<String> ambiguous;
private HashMap<String, Option> unambiguousOptionMap;
private HashMap<String, Option> ambiguousOptionMap;
private HashMap<String, Option> flagOptionMap;
/**
* Creates the Option Descriptions
*/
public OptionDescriptions() {
optionGroups = new HashMap<String, OptionGroup>();
ambiguous = new TreeSet<String>();
unambiguousOptionMap = new HashMap<String, Option>();
ambiguousOptionMap = new HashMap<String, Option>();
flagOptionMap = new HashMap<String, Option>();
}
/**
* Returns an option based on the option name and/or the option group name
*
* @param optiongroup the name of the option group
* @param optionname the option name
* @return an option based on the option name and/or the option group name
* @throws MaltChainedException
*/
public Option getOption(String optiongroup, String optionname) throws MaltChainedException {
if (optionname == null || optionname.length() <= 0) {
throw new OptionException("The option name '" + optionname + "' cannot be found");
}
Option option;
if (ambiguous.contains(optionname.toLowerCase())) {
if (optiongroup == null || optiongroup.length() <= 0) {
throw new OptionException("The option name '" + optionname + "' is ambiguous use option group name to distinguish the option. ");
} else {
option = ambiguousOptionMap.get(optiongroup.toLowerCase() + "-" + optionname.toLowerCase());
if (option == null) {
throw new OptionException("The option '--" + optiongroup.toLowerCase() + "-" + optionname.toLowerCase() + " does not exist. ");
}
}
} else {
option = unambiguousOptionMap.get(optionname.toLowerCase());
if (option == null) {
throw new OptionException("The option '--" + optionname.toLowerCase() + " doesn't exist. ");
}
}
return option;
}
/**
* Returns an option based on the option flag
*
* @param optionflag the option flag
* @return an option based on the option flag
* @throws MaltChainedException
*/
public Option getOption(String optionflag) throws MaltChainedException {
Option option = flagOptionMap.get(optionflag);
if (option == null) {
throw new OptionException("The option flag -" + optionflag + " could not be found. ");
}
return option;
}
/**
* Returns a set of option that are marked as SAVEOPTION
*
* @return a set of option that are marked as SAVEOPTION
*/
public Set<Option> getSaveOptionSet() {
Set<Option> optionToSave = new HashSet<Option>();
for (String optionname : unambiguousOptionMap.keySet()) {
if (unambiguousOptionMap.get(optionname).getUsage() == Option.SAVE) {
optionToSave.add(unambiguousOptionMap.get(optionname));
}
}
for (String optionname : ambiguousOptionMap.keySet()) {
if (ambiguousOptionMap.get(optionname).getUsage() == Option.SAVE) {
optionToSave.add(ambiguousOptionMap.get(optionname));
}
}
return optionToSave;
}
/**
* Return a sorted set of option group names
*
* @return a sorted set of option group names
*/
public TreeSet<String> getOptionGroupNameSet() {
return new TreeSet<String>(optionGroups.keySet());
}
/**
* Returns a collection of option that are member of an option group
*
* @param groupname the name of the option group
* @return a collection of option that are member of an option group
*/
public Collection<Option> getOptionGroupList(String groupname) {
return optionGroups.get(groupname).getOptionList();
}
/**
* Parse a XML file that contains the options used for controlling the
* application. The method calls the parseOptionGroups to parse the set of
* option groups in the DOM tree.
*
* @param url The path to a XML file that explains the options used in the
* application.
* @throws OptionException
*/
public void parseOptionDescriptionXMLfile(URL url) throws MaltChainedException {
if (url == null) {
throw new OptionException("The URL to the default option file is null. ");
}
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Element root = db.parse(url.openStream()).getDocumentElement();
NodeList groups = root.getElementsByTagName("optiongroup");
Element group;
for (int i = 0; i < groups.getLength(); i++) {
group = (Element) groups.item(i);
String groupname = group.getAttribute("groupname").toLowerCase();
OptionGroup og;
if (optionGroups.containsKey(groupname)) {
og = optionGroups.get(groupname);
} else {
optionGroups.put(groupname, new OptionGroup(groupname));
og = optionGroups.get(groupname);
}
parseOptionsDescription(group, og);
}
} catch (java.io.IOException e) {
throw new OptionException("Can't find the file " + url.toString() + ".", e);
} catch (OptionException e) {
throw new OptionException("Problem parsing the file " + url.toString() + ". ", e);
} catch (ParserConfigurationException e) {
throw new OptionException("Problem parsing the file " + url.toString() + ". ", e);
} catch (SAXException e) {
throw new OptionException("Problem parsing the file " + url.toString() + ". ", e);
}
}
/**
* Parse a set of options within an option group to collect all information
* of individual options.
*
* @param group a reference to an individual option group in the DOM tree.
* @param og a reference to the corresponding option group in the HashMap.
* @throws OptionException
*/
private void parseOptionsDescription(Element group, OptionGroup og) throws MaltChainedException {
NodeList options = group.getElementsByTagName("option");
Element option;
for (int i = 0; i < options.getLength(); i++) {
option = (Element) options.item(i);
String optionname = option.getAttribute("name").toLowerCase();
String optiontype = option.getAttribute("type").toLowerCase();
String defaultValue = option.getAttribute("default");
String usage = option.getAttribute("usage").toLowerCase();
String flag = option.getAttribute("flag");
NodeList shortdescs = option.getElementsByTagName("shortdesc");
Element shortdesc;
String shortdesctext = "";
if (shortdescs.getLength() == 1) {
shortdesc = (Element) shortdescs.item(0);
shortdesctext = shortdesc.getTextContent();
}
if (optiontype.equals("string") || optiontype.equals("bool") || optiontype.equals("integer") || optiontype.equals("unary")) {
Option op = og.getOption(optionname);
if (op != null) {
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. ");
}
} else if (optiontype.equals("class") || optiontype.equals("enum") || optiontype.equals("stringenum")) {
Option op = og.getOption(optionname);
if (op != null) {
if (op instanceof EnumOption && !optiontype.equals("enum")) {
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. ");
}
if (op instanceof ClassOption && !optiontype.equals("class")) {
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. ");
}
if (op instanceof StringEnumOption && !optiontype.equals("stringenum")) {
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. ");
}
}
}
if (optiontype.equals("string")) {
og.addOption(new StringOption(og, optionname, shortdesctext, flag, usage, defaultValue));
} else if (optiontype.equals("bool")) {
og.addOption(new BoolOption(og, optionname, shortdesctext, flag, usage, defaultValue));
} else if (optiontype.equals("integer")) {
og.addOption(new IntegerOption(og, optionname, shortdesctext, flag, usage, defaultValue));
} else if (optiontype.equals("unary")) {
og.addOption(new UnaryOption(og, optionname, shortdesctext, flag, usage));
} else if (optiontype.equals("enum")) {
Option op = og.getOption(optionname);
EnumOption eop = null;
if (op == null) {
eop = new EnumOption(og, optionname, shortdesctext, flag, usage);
} else {
if (op instanceof EnumOption) {
eop = (EnumOption) op;
}
}
NodeList legalvalues = option.getElementsByTagName("legalvalue");
Element legalvalue;
for (int j = 0; j < legalvalues.getLength(); j++) {
legalvalue = (Element) legalvalues.item(j);
String legalvaluename = legalvalue.getAttribute("name");
String legalvaluetext = legalvalue.getTextContent();
eop.addLegalValue(legalvaluename, legalvaluetext);
}
if (op == null) {
eop.setDefaultValue(defaultValue);
og.addOption(eop);
}
} else if (optiontype.equals("class")) {
Option op = og.getOption(optionname);
ClassOption cop = null;
if (op == null) {
cop = new ClassOption(og, optionname, shortdesctext, flag, usage);
} else {
if (op instanceof ClassOption) {
cop = (ClassOption) op;
}
}
NodeList legalvalues = option.getElementsByTagName("legalvalue");
Element legalvalue;
for (int j = 0; j < legalvalues.getLength(); j++) {
legalvalue = (Element) legalvalues.item(j);
String legalvaluename = legalvalue.getAttribute("name").toLowerCase();
String classname = legalvalue.getAttribute("class");
String legalvaluetext = legalvalue.getTextContent();
cop.addLegalValue(legalvaluename, legalvaluetext, classname);
}
if (op == null) {
cop.setDefaultValue(defaultValue);
og.addOption(cop);
}
} else if (optiontype.equals("stringenum")) {
Option op = og.getOption(optionname);
StringEnumOption ueop = null;
if (op == null) {
ueop = new StringEnumOption(og, optionname, shortdesctext, flag, usage);
} else {
if (op instanceof StringEnumOption) {
ueop = (StringEnumOption) op;
}
}
NodeList legalvalues = option.getElementsByTagName("legalvalue");
Element legalvalue;
for (int j = 0; j < legalvalues.getLength(); j++) {
legalvalue = (Element) legalvalues.item(j);
String legalvaluename = legalvalue.getAttribute("name").toLowerCase();
String url = legalvalue.getAttribute("mapto");
String legalvaluetext = legalvalue.getTextContent();
ueop.addLegalValue(legalvaluename, legalvaluetext, url);
}
if (op == null) {
ueop.setDefaultValue(defaultValue);
og.addOption(ueop);
}
} else {
throw new OptionException("Illegal option type found in the setting file. ");
}
}
}
/**
* Creates several option maps for fast access to individual options.
*
* @throws OptionException
*/
public void generateMaps() throws MaltChainedException {
for (String groupname : optionGroups.keySet()) {
OptionGroup og = optionGroups.get(groupname);
Collection<Option> options = og.getOptionList();
for (Option option : options) {
if (ambiguous.contains(option.getName())) {
option.setAmbiguous(true);
ambiguousOptionMap.put(option.getGroup().getName() + "-" + option.getName(), option);
} else {
if (!unambiguousOptionMap.containsKey(option.getName())) {
unambiguousOptionMap.put(option.getName(), option);
} else {
Option ambig = unambiguousOptionMap.get(option.getName());
unambiguousOptionMap.remove(ambig);
ambig.setAmbiguous(true);
option.setAmbiguous(true);
ambiguous.add(option.getName());
ambiguousOptionMap.put(ambig.getGroup().getName() + "-" + ambig.getName(), ambig);
ambiguousOptionMap.put(option.getGroup().getName() + "-" + option.getName(), option);
}
}
if (option.getFlag() != null) {
Option co = flagOptionMap.get(option.getFlag());
if (co != null) {
flagOptionMap.remove(co);
co.setFlag(null);
option.setFlag(null);
if (SystemLogger.logger().isDebugEnabled()) {
SystemLogger.logger().debug("Ambiguous use of an option flag -> the option flag is removed for all ambiguous options\n");
}
} else {
flagOptionMap.put(option.getFlag(), option);
}
}
}
}
}
/**
* Returns a string representation that contains printable information of
* several options maps
*
* @return a string representation that contains printable information of
* several options maps
*/
public String toStringMaps() {
final StringBuilder sb = new StringBuilder();
sb.append("UnambiguousOptionMap\n");
for (String optionname : new TreeSet<String>(unambiguousOptionMap.keySet())) {
sb.append(" ").append(optionname).append("\n");
}
sb.append("AmbiguousSet\n");
for (String optionname : ambiguous) {
sb.append(" ").append(optionname).append("\n");
}
sb.append("AmbiguousOptionMap\n");
for (String optionname : new TreeSet<String>(ambiguousOptionMap.keySet())) {
sb.append(" ").append(optionname).append("\n");
}
sb.append("CharacterOptionMap\n");
for (String flag : new TreeSet<String>(flagOptionMap.keySet())) {
sb.append(" -").append(flag).append(" -> ").append(flagOptionMap.get(flag).getName()).append("\n");
}
return sb.toString();
}
/**
* Returns a string representation of a option group without the option
* group name in the string.
*
* @param groupname The option group name
* @return a string representation of a option group
*/
public String toStringOptionGroup(String groupname) {
OptionGroup.toStringSetting = OptionGroup.NOGROUPNAME;
return optionGroups.get(groupname).toString() + "\n";
}
/*
* (non-Javadoc) @see java.lang.Object#toString()
*/
@Override
public String toString() {
OptionGroup.toStringSetting = OptionGroup.WITHGROUPNAME;
final StringBuilder sb = new StringBuilder();
for (String groupname : new TreeSet<String>(optionGroups.keySet())) {
sb.append(optionGroups.get(groupname).toString()).append("\n");
}
return sb.toString();
}
}