/*
* ElementRule.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.xml;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
/**
* A syntax rule to ensure that exactly one of the given element
* appears as a child.
*
* @version $Id: ElementRule.java,v 1.22 2005/05/24 20:26:01 rambaut Exp $
*
* @author Alexei Drummond
* @author Andrew Rambaut
*/
public class ElementRule implements XMLSyntaxRule {
/**
* Creates a required element rule.
* @param type Class
*/
public ElementRule(Class type) {
this(type, null, null, 1, 1);
}
/**
* Creates a required element rule.
* @param type Class
* @param optional boolean
*/
public ElementRule(Class type, boolean optional) {
this(type, null, null, (optional ? 0 : 1), 1);
}
/**
* Creates a required element rule.
* @param type Class
* @param description String
*/
public ElementRule(Class type, String description) {
this(type, description, null, 1, 1);
}
/**
* Creates a required element rule.
* @param type Class
* @param min int
* @param max int
*/
public ElementRule(Class type, int min, int max) {
this(type, null, null, min, max);
}
/**
* Creates a required element rule.
* @param type Class
* @param description String
* @param example String
*/
public ElementRule(Class type, String description, String example) {
this(type, description, example, 1, 1);
}
/**
* Creates a required element rule.
* @param type Class
* @param description String
* @param min int
* @param max int
*/
public ElementRule(Class type, String description, int min, int max) {
this(type, description, null, min, max);
}
/**
* Creates a required element rule.
*/
public ElementRule(Class type, String description, String example, int min, int max) {
if (type == null) throw new IllegalArgumentException("Class cannot be null!");
this.c = type;
this.min = min;
this.max = max;
this.description = description;
this.example = example;
}
/**
* Creates a required element rule.
*/
public ElementRule(String name, Class type) {
this.name = name;
this.rules = new XMLSyntaxRule[] { new ElementRule(type)};
}
/**
* Creates a required element rule.
*/
public ElementRule(String name, Class type, String description) {
this.name = name;
this.description = description;
this.rules = new XMLSyntaxRule[] { new ElementRule(type)};
}
/**
* Creates a required element rule.
*/
public ElementRule(String name, Class type, String description, boolean optional) {
this.name = name;
this.description = description;
this.rules = new XMLSyntaxRule[] { new ElementRule(type)};
this.min = 1;
this.max = 1;
if (optional) this.min = 0;
}
/**
* Creates a required element rule.
*/
public ElementRule(String name, Class type, String description, int min, int max) {
this.name = name;
this.description = description;
this.rules = new XMLSyntaxRule[] { new ElementRule(type)};
this.min = min;
this.max = max;
}
/**
* Creates a required element rule.
*/
public ElementRule(String name, XMLSyntaxRule[] rules) {
this.name = name;
this.rules = rules;
}
/**
* Creates an element rule.
*/
public ElementRule(String name, XMLSyntaxRule[] rules, boolean optional) {
this.name = name;
this.rules = rules;
this.min = 1;
this.max = 1;
if (optional) this.min = 0;
}
/**
* Creates an element rule.
*/
public ElementRule(String name, XMLSyntaxRule[] rules, int min, int max) {
this.name = name;
this.rules = rules;
this.min = min;
this.max = max;
}
/**
* Creates a required element rule.
* @param name String
* @param rules XMLSyntaxRule[]
* @param description String
*/
public ElementRule(String name, XMLSyntaxRule[] rules, String description) {
this.name = name;
this.rules = rules;
this.description = description;
}
public ElementRule(String name, XMLSyntaxRule[] rules, String description, boolean optional) {
this.name = name;
this.rules = rules;
this.description = description;
this.min = 1;
this.max = 1;
if (optional) this.min = 0;
}
/**
* Creates an element rule.
* @param name String
* @param rules XMLSyntaxRule[]
* @param description String
* @param min int
* @param max int
*/
public ElementRule(String name, XMLSyntaxRule[] rules, String description, int min, int max) {
this.name = name;
this.rules = rules;
this.description = description;
this.min = min;
this.max = max;
}
public Class getElementClass() { return c; }
public String getDescription() {
return description;
}
public boolean hasDescription() { return description != null; }
public String getExample() {
return example;
}
public boolean hasExample() { return example != null; }
/**
* @return true if the required attribute of the correct type is present.
*/
public boolean isSatisfied(XMLObject xo) {
// first check if no matches and its optional
int nameCount = 0;
for (int i = 0; i < xo.getChildCount(); i++) {
Object xoc = xo.getChild(i);
if (xoc instanceof XMLObject && ((XMLObject)xoc).getName().equals(name)) {
nameCount += 1;
}
}
if (min == 0 && nameCount == 0) return true;
// if !optional or nameCount > 0 then check if exactly one match exists
int matchCount = 0;
for (int i = 0; i < xo.getChildCount(); i++) {
Object xoc = xo.getChild(i);
if (isCompatible(xoc)) {
matchCount += 1;
}
}
return (matchCount >= min && matchCount <= max);
}
public boolean containsAttribute(String name) {
return false;
}
/**
* @return a string describing the rule.
*/
public String ruleString() {
String howMany;
if( min == 1 && max == 1 ) {
howMany = "Exactly one";
} else if (min == max) {
howMany = "Exactly " + min;
} else if( (min <= 1) && max == Integer.MAX_VALUE ) {
howMany = "Any number of";
} else {
howMany = "between " + min + " and " + max;
}
if (c != null) {
return howMany + " ELEMENT of type " + getTypeName() + " REQUIRED";
} else {
StringBuffer buffer = new StringBuffer(howMany + " ELEMENT of name " + name + " REQUIRED containing");
for (XMLSyntaxRule rule : rules) {
buffer.append("\n ").append(rule.ruleString());
}
return buffer.toString();
}
}
/**
* @return a string describing the rule.
*/
public String htmlRuleString(XMLDocumentationHandler handler) {
if (c != null) {
String html = "<div class=\"" + (min == 0 ? "optional" : "required") + "rule\">" + handler.getHTMLForClass(c);
if (max > 1) {
html += " elements (";
if (min == 0) {
html += "zero";
} else if (min == 1) {
html += "one";
} else if (min == max) {
html += "exactly " + min;
}
if (max != min) {
if (max < Integer.MAX_VALUE) {
html += " to " + max;
} else {
html += " or more";
}
}
} else {
html += " element (";
if (min == 0) {
html += "zero or one";
} else {
html += "exactly one";
}
}
html += ")";
if (hasDescription()) {
html += "<div class=\"description\">" + getDescription() + "</div>\n";
}
return html + "</div>\n";
} else {
StringBuffer buffer = new StringBuffer("<div class=\"" + (min == 0 ? "optional" : "required") + "compoundrule\">Element named <span class=\"elemname\">" + name + "</span> containing:");
for (XMLSyntaxRule rule : rules) {
buffer.append(rule.htmlRuleString(handler));
}
if (hasDescription()) {
buffer.append("<div class=\"description\">").append(getDescription()).append("</div>\n");
}
buffer.append("</div>\n");
return buffer.toString();
}
}
public String wikiRuleString(XMLDocumentationHandler handler, String prefix) {
if (c != null) {
String wiki = prefix + handler.getHTMLForClass(c);
if (max > 1) {
wiki += " elements (";
if (min == 0) {
wiki += "zero";
} else if (min == 1) {
wiki += "one";
} else if (min == max) {
wiki += "exactly " + min;
}
if (max != min) {
if (max < Integer.MAX_VALUE) {
wiki += " to " + max;
} else {
wiki += " or more";
}
}
} else {
wiki += " element (";
if (min == 0) {
wiki += "zero or one";
} else {
wiki += "exactly one";
}
}
wiki += ")\n";
if (hasDescription()) {
wiki += prefix + ":''" + getDescription() + "''\n";
} else {
wiki += prefix + ":\n";
}
return wiki;
} else {
StringBuffer buffer = new StringBuffer(prefix + "Element named <code><" + name + "></code> containing:\n");
for (XMLSyntaxRule rule : rules) {
buffer.append(rule.wikiRuleString(handler, prefix + "*"));
}
if (hasDescription()) {
buffer.append(prefix).append("*:''").append(getDescription()).append("''\n");
} else {
buffer.append(prefix).append("*:\n");
}
return buffer.toString();
}
}
/**
* @return a string describing the rule.
*/
public String ruleString(XMLObject xo) {
return ruleString();
}
public boolean isAttributeRule() { return false; }
public String getName() { return name; }
public XMLSyntaxRule[] getRules() { return rules; }
/**
* @param o Object
* @return true if the given object is compatible with the required class.
*/
private boolean isCompatible(Object o) {
if (rules != null) {
if (o instanceof XMLObject) {
XMLObject xo = (XMLObject)o;
if (xo.getName().equals(name)) {
for (XMLSyntaxRule rule : rules) {
if (!rule.isSatisfied(xo)) {
return false;
}
}
return true;
}
}
} else {
if (c == null) {
return true;
}
if (c.isInstance(o)) {
return true;
}
if (o instanceof String) {
if (c == Double.class) {
try {
Double.parseDouble((String)o);
return true;
} catch (NumberFormatException nfe) { return false; }
}
if (c == Integer.class) {
try {
Integer.parseInt((String)o);
return true;
} catch (NumberFormatException nfe) { return false; }
}
if (c == Float.class) {
try {
Float.parseFloat((String)o);
return true;
} catch (NumberFormatException nfe) { return false; }
}
if (c == Boolean.class) {
return (o.equals("true") || o.equals("false"));
}
if (c == Number.class) {
try {
Double.parseDouble((String)o);
return true;
} catch (NumberFormatException nfe) { return false; }
}
}
}
return false;
}
/**
* @return a pretty name for a class.
*/
private String getTypeName() {
if (c == null) return "Object";
String name = c.getName();
return name.substring(name.lastIndexOf('.')+1);
}
/**
* @return a set containing the required types of this rule.
*/
public Set<Class> getRequiredTypes() {
if (c != null) {
return Collections.singleton(c);
} else {
Set<Class> set = new TreeSet<Class>(ClassComparator.INSTANCE);
for (XMLSyntaxRule rule : rules) {
set.addAll(rule.getRequiredTypes());
}
return set;
}
}
public boolean isLegalElementName(String elementName) {
return c == null && name != null && name.equals(elementName);
}
public boolean isLegalElementClass(Class c) {
return this.c != null && this.c.isAssignableFrom(c);
}
public boolean isLegalSubelementName(String elementName) {
for( XMLSyntaxRule r : rules ) {
if( r.isLegalElementName(elementName) ) {
return true;
}
}
return false;
}
public int getMin() { return min; }
public int getMax() { return max; }
public String toString() { return ruleString(); }
private Class c = null;
private String name = null;
private XMLSyntaxRule[] rules = null;
private int min = 1;
private int max = 1;
private String description = null;
private String example = null;
}