/*
* 02/06/2010
*
* CompletionXMLParser.java - Parses XML representing code completion for a
* C-like language.
*
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.autocomplete;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Parser for an XML file describing a procedural language such as C. XML
* files will be validated against the <code>CompletionXml.dtd</code> DTD
* found in this package.
*
* @author Robert Futrell
* @version 1.0
*/
public class CompletionXMLParser extends DefaultHandler {
/**
* The completions found after parsing the XML.
*/
private List<Completion> completions;
/**
* The provider we're getting completions for.
*/
private CompletionProvider provider;
/**
* The completion provider to use when loading classes, such as custom
* {@link FunctionCompletion}s.
*/
private ClassLoader completionCL;
private String name;
private String type;
private String returnType;
private StringBuilder returnValDesc;
private StringBuilder desc;
private String paramName;
private String paramType;
private StringBuilder paramDesc;
private List<ParameterizedCompletion.Parameter> params;
private String definedIn;
private boolean doingKeywords;
private boolean inKeyword;
private boolean gettingReturnValDesc;
private boolean gettingDesc;
private boolean gettingParams;
private boolean inParam;
private boolean gettingParamDesc;
private boolean inCompletionTypes;
private char paramStartChar;
private char paramEndChar;
private String paramSeparator;
/**
* If specified in the XML, this class will be used instead of
* {@link FunctionCompletion} when appropriate. This class should extend
* <tt>FunctionCompletion</tt>, or stuff will break.
*/
private String funcCompletionType;
/**
* The class loader to use to load custom completion classes, such as
* the one defined by {@link #funcCompletionType}. If this is
* <code>null</code>, then a default class loader is used. This field
* will usually be <code>null</code>.
*/
private static ClassLoader DEFAULT_COMPLETION_CLASS_LOADER;
/**
* Constructor.
*
* @param provider The provider to get completions for.
* @see #reset(CompletionProvider)
*/
public CompletionXMLParser(CompletionProvider provider) {
this(provider, null);
}
/**
* Constructor.
*
* @param provider The provider to get completions for.
* @param cl The class loader to use, if necessary, when loading classes
* from the XML (custom {@link FunctionCompletion}s, for example).
* This may be <code>null</code> if the default is to be used, or
* if the XML does not define specific classes for completion types.
* @see #reset(CompletionProvider)
*/
public CompletionXMLParser(CompletionProvider provider, ClassLoader cl) {
this.provider = provider;
this.completionCL = cl;
if (completionCL==null) {
// May also be null, but that's okay.
completionCL = DEFAULT_COMPLETION_CLASS_LOADER;
}
completions = new ArrayList<Completion>();
params = new ArrayList<ParameterizedCompletion.Parameter>(1);
desc = new StringBuilder();
paramDesc = new StringBuilder();
returnValDesc = new StringBuilder();
paramStartChar = paramEndChar = 0;
paramSeparator = null;
}
/**
* Called when character data inside an element is found.
*/
@Override
public void characters(char[] ch, int start, int length) {
if (gettingDesc) {
desc.append(ch, start, length);
}
else if (gettingParamDesc) {
paramDesc.append(ch, start, length);
}
else if (gettingReturnValDesc) {
returnValDesc.append(ch, start, length);
}
}
private FunctionCompletion createFunctionCompletion() {
FunctionCompletion fc = null;
if (funcCompletionType!=null) {
try {
Class<?> clazz = null;
if (completionCL!=null) {
clazz = Class.forName(funcCompletionType, true,
completionCL);
}
else {
clazz = Class.forName(funcCompletionType);
}
Constructor<?> c = clazz.getDeclaredConstructor(
CompletionProvider.class, String.class, String.class);
fc = (FunctionCompletion)c.newInstance(provider, name,
returnType);
} catch (RuntimeException re) { // FindBugs
throw re;
} catch (Exception e) {
e.printStackTrace();
}
}
if (fc==null) { // Fallback if completion failed for some reason
fc = new FunctionCompletion(provider, name, returnType);
}
if (desc.length()>0) {
fc.setShortDescription(desc.toString());
desc.setLength(0);
}
fc.setParams(params);
fc.setDefinedIn(definedIn);
if (returnValDesc.length()>0) {
fc.setReturnValueDescription(returnValDesc.toString());
returnValDesc.setLength(0);
}
return fc;
}
private BasicCompletion createOtherCompletion() {
BasicCompletion bc = new BasicCompletion(provider, name);
if (desc.length()>0) {
bc.setSummary(desc.toString());
desc.setLength(0);
}
return bc;
}
private MarkupTagCompletion createMarkupTagCompletion() {
MarkupTagCompletion mc = new MarkupTagCompletion(provider,
name);
if (desc.length()>0) {
mc.setDescription(desc.toString());
desc.setLength(0);
}
mc.setAttributes(params);
mc.setDefinedIn(definedIn);
return mc;
}
private VariableCompletion createVariableCompletion() {
VariableCompletion vc = new VariableCompletion(provider,
name, returnType);
if (desc.length()>0) {
vc.setShortDescription(desc.toString());
desc.setLength(0);
}
vc.setDefinedIn(definedIn);
return vc;
}
/**
* Called when an element is closed.
*/
@Override
public void endElement(String uri, String localName, String qName) {
if ("keywords".equals(qName)) {
doingKeywords = false;
}
else if (doingKeywords) {
if ("keyword".equals(qName)) {
Completion c = null;
if ("function".equals(type)) {
c = createFunctionCompletion();
}
else if ("constant".equals(type)) {
c = createVariableCompletion();
}
else if ("tag".equals(type)) { // Markup tag, such as HTML
c = createMarkupTagCompletion();
}
else if ("other".equals(type)) {
c = createOtherCompletion();
}
else {
throw new InternalError("Unexpected type: " + type);
}
completions.add(c);
inKeyword = false;
}
else if (inKeyword) {
if ("returnValDesc".equals(qName)) {
gettingReturnValDesc = false;
}
else if (gettingParams) {
if ("params".equals(qName)) {
gettingParams = false;
}
else if ("param".equals(qName)) {
FunctionCompletion.Parameter param =
new FunctionCompletion.Parameter(paramType,
paramName);
if (paramDesc.length()>0) {
param.setDescription(paramDesc.toString());
paramDesc.setLength(0);
}
params.add(param);
inParam = false;
}
else if (inParam) {
if ("desc".equals(qName)) {
gettingParamDesc = false;
}
}
}
else if ("desc".equals(qName)) {
gettingDesc = false;
}
}
}
else if (inCompletionTypes) {
if ("completionTypes".equals(qName)) {
inCompletionTypes = false;
}
}
}
@Override
public void error(SAXParseException e) throws SAXException {
throw e;
}
/**
* Returns the completions found after parsing the XML.
*
* @return The completions.
*/
public List<Completion> getCompletions() {
return completions;
}
/**
* Returns the parameter end character specified.
*
* @return The character, or 0 if none was specified.
*/
public char getParamEndChar() {
return paramEndChar;
}
/**
* Returns the parameter end string specified.
*
* @return The string, or <code>null</code> if none was specified.
*/
public String getParamSeparator() {
return paramSeparator;
}
/**
* Returns the parameter start character specified.
*
* @return The character, or 0 if none was specified.
*/
public char getParamStartChar() {
return paramStartChar;
}
private static final char getSingleChar(String str) {
return str.length()==1 ? str.charAt(0) : 0;
}
/**
* Resets this parser to grab more completions.
*
* @param provider The new provider to get completions for.
*/
public void reset(CompletionProvider provider) {
this.provider = provider;
completions.clear();
doingKeywords = inKeyword = gettingDesc = gettingParams = inParam =
gettingParamDesc = false;
paramStartChar = paramEndChar = 0;
paramSeparator = null;
}
@Override
public InputSource resolveEntity(String publicID,
String systemID) throws SAXException {
return new InputSource(getClass().
getResourceAsStream("CompletionXml.dtd"));
}
/**
* Sets the class loader to use when loading custom classes to use for
* various {@link Completion} types, such as {@link FunctionCompletion}s,
* from XML.<p>
*
* Users should very rarely have a need to use this method.
*
* @param cl The class loader to use. If this is <code>null</code>, then
* a default is used.
*/
public static void setDefaultCompletionClassLoader(ClassLoader cl) {
DEFAULT_COMPLETION_CLASS_LOADER = cl;
}
/**
* Called when an element starts.
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attrs) {
if ("keywords".equals(qName)) {
doingKeywords = true;
}
else if (doingKeywords) {
if ("keyword".equals(qName)) {
name = attrs.getValue("name");
type = attrs.getValue("type");
returnType = attrs.getValue("returnType");
params.clear();
definedIn = attrs.getValue("definedIn");
inKeyword = true;
}
else if (inKeyword) {
if ("returnValDesc".equals(qName)) {
gettingReturnValDesc = true;
}
else if ("params".equals(qName)) {
gettingParams = true;
}
else if (gettingParams) {
if ("param".equals(qName)) {
paramName = attrs.getValue("name");
paramType = attrs.getValue("type");
inParam = true;
}
if (inParam) {
if ("desc".equals(qName)) {
gettingParamDesc = true;
}
}
}
else if ("desc".equals(qName)) {
gettingDesc = true;
}
}
}
else if ("environment".equals(qName)) {
paramStartChar = getSingleChar(attrs.getValue("paramStartChar"));
paramEndChar = getSingleChar(attrs.getValue("paramEndChar"));
paramSeparator = attrs.getValue("paramSeparator");
//paramTerminal = attrs.getValua("terminal");
}
else if ("completionTypes".equals(qName)) {
inCompletionTypes = true;
}
else if (inCompletionTypes) {
if ("functionCompletionType".equals(qName)) {
funcCompletionType = attrs.getValue("type");
}
}
}
@Override
public void warning(SAXParseException e) throws SAXException {
throw e;
}
}