/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.documentation;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
/**
* Models a link from any text of a documentation to a documented element.
* The target of the link can be either a class, or a method. If the target
* is a method, then the signature of the method is relevant, so that overloading
* is supported.
*
* @author gerzse
*/
public class QualifiedDocumentationName implements Comparable<QualifiedDocumentationName>
{
public static final String JS_PREFIX = "js_"; //$NON-NLS-1$
public static final String JS_FUNCTION_PREFIX = "jsFunction_"; //$NON-NLS-1$
public static final String JS_CONSTRUCTOR_PREFIX = "jsConstructor_"; //$NON-NLS-1$
public static final String[] NO_ARGUMENTS = new String[] { };
private static final String ATTR_OBJECT = "object"; //$NON-NLS-1$
private static final String ATTR_MEMBER = "member"; //$NON-NLS-1$
private static final String ATTR_ARGUMENTSTYPES = "argumentsTypes"; //$NON-NLS-1$
private final String className;
private final String memberName;
private final String[] argumentsTypes;
/**
* This constructor should never be called directly. Use the provided static methods
* for creating instances of this class.
*/
private QualifiedDocumentationName(String className, String memberName, String[] argumentsTypes)
{
if (className == null)
{
throw new IllegalArgumentException("Class name cannot be null."); //$NON-NLS-1$
}
if (memberName == null)
{
if (argumentsTypes != null) throw new IllegalArgumentException("If no method is specified, then you can't specify a signature."); //$NON-NLS-1$
}
else
{
if (argumentsTypes == null) throw new IllegalArgumentException("Signature cannot be null if a method is specified."); //$NON-NLS-1$
}
this.className = className;
this.argumentsTypes = argumentsTypes;
if (memberName != null && memberName.startsWith(JS_PREFIX)) this.memberName = memberName.substring(JS_PREFIX.length());
else if (memberName != null && memberName.startsWith(JS_FUNCTION_PREFIX)) this.memberName = memberName.substring(JS_FUNCTION_PREFIX.length());
else this.memberName = memberName;
}
public String getClassName()
{
return className;
}
public String getMemberName()
{
return memberName;
}
public String[] getArgumentsTypes()
{
return argumentsTypes;
}
public int compareTo(QualifiedDocumentationName o)
{
int result = this.className.compareTo(o.className);
if (result == 0 && memberName != null)
{
if (o.memberName == null)
{
result = 1;
}
else
{
result = this.memberName.compareTo(o.memberName);
}
}
if (result == 0 && argumentsTypes != null)
{
if (o.argumentsTypes == null)
{
result = 1;
}
else
{
if (this.argumentsTypes.length < o.argumentsTypes.length)
{
result = -1;
}
else if (this.argumentsTypes.length > o.argumentsTypes.length)
{
result = 1;
}
else
{
for (int i = 0; i < this.argumentsTypes.length; i++)
{
int sigcmp = this.argumentsTypes[i].compareTo(o.argumentsTypes[i]);
if (sigcmp != 0)
{
result = sigcmp;
break;
}
}
}
}
}
return result;
}
@Override
public boolean equals(Object other)
{
if (other == null)
{
return false;
}
else
{
if (other instanceof QualifiedDocumentationName)
{
QualifiedDocumentationName qother = (QualifiedDocumentationName)other;
return this.compareTo(qother) == 0;
}
else
{
return false;
}
}
}
@Override
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append(className);
if (memberName != null)
{
sb.append("#").append(memberName); //$NON-NLS-1$
if (argumentsTypes != NO_ARGUMENTS)
{
sb.append("("); //$NON-NLS-1$
for (int i = 0; i < argumentsTypes.length; i++)
{
if (i > 0) sb.append(","); //$NON-NLS-1$
sb.append(argumentsTypes[i]);
}
sb.append(")"); //$NON-NLS-1$
}
else
{
sb.append("[no arguments]");
}
}
return sb.toString();
}
public Element toXML(String elementName)
{
Element root = DocumentHelper.createElement(elementName);
if (memberName != null)
{
root.addAttribute(ATTR_MEMBER, memberName);
String argsAsString = argumentsArray2String(argumentsTypes);
// if (argsAsString != null) root.addAttribute(ATTR_ARGUMENTSTYPES, argsAsString);
}
if (className != null) root.addAttribute(ATTR_OBJECT, className);
return root;
}
public static QualifiedDocumentationName fromXML(Element element)
{
String objectName = element.attributeValue(ATTR_OBJECT);
String memberName = element.attributeValue(ATTR_MEMBER);
String[] args = null;
if (memberName != null)
{
String argsAsString = element.attributeValue(ATTR_ARGUMENTSTYPES);
args = argumentsString2Array(argsAsString);
}
return new QualifiedDocumentationName(objectName, memberName, args);
}
public static QualifiedDocumentationName fromString(String text, String defaultTargetObject)
{
String objName = null;
String memberName = null;
String[] args = null;
Pattern pattern = Pattern.compile("^([^\\(]+)\\(([^\\)]*)\\)$"); //$NON-NLS-1$
int firstIndex = text.indexOf('#');
int lastIndex = text.lastIndexOf('#');
if (firstIndex != lastIndex)
{
System.out.println("More than one # characters used in documentation reference '" + text + "' at positions " + firstIndex + " and " + lastIndex + //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
" in object '" + defaultTargetObject + "'."); //$NON-NLS-1$ //$NON-NLS-2$
objName = text.substring(0, firstIndex).trim();
}
else
{
// If there is no # then we have either an object name, either a member name.
if (lastIndex < 0)
{
// If there are no dots in the text, then it's only a method name. Use the default object.
if (text.indexOf(".") < 0) //$NON-NLS-1$
{
objName = defaultTargetObject;
memberName = text.trim();
}
// If there are dots, then it's an object name, without a member.
else
{
objName = text.trim();
}
}
else
{
objName = text.substring(0, lastIndex).trim();
memberName = text.substring(lastIndex + 1).trim();
}
if (memberName != null)
{
// If there are arguments, parse them.
Matcher matcher = pattern.matcher(memberName);
if (matcher.find())
{
memberName = matcher.group(1);
String rawArgs = matcher.group(2);
args = argumentsString2Array(rawArgs);
}
else
{
args = NO_ARGUMENTS;
}
// Remove js_ prefixes from method name.
if (memberName.startsWith("js_")) memberName = memberName.substring("js_".length()); //$NON-NLS-1$//$NON-NLS-2$
if (memberName.startsWith("jsFunction_")) memberName = memberName.substring("jsFunction_".length()); //$NON-NLS-1$//$NON-NLS-2$
}
}
QualifiedDocumentationName result = new QualifiedDocumentationName(objName, memberName, args);
return result;
}
public static String[] argumentsString2Array(String args)
{
if (args == null)
{
return NO_ARGUMENTS;
}
else
{
if (args.trim().length() == 0)
{
return NO_ARGUMENTS;
}
else
{
String[] parts = args.split(","); //$NON-NLS-1$
List<String> collectedParts = new ArrayList<String>();
for (String part : parts)
collectedParts.add(part.trim());
String[] result = new String[collectedParts.size()];
collectedParts.toArray(result);
return result;
}
}
}
public static String argumentsArray2String(String[] args)
{
if (args == null || args == NO_ARGUMENTS)
{
return null;
}
else
{
StringBuffer sb = new StringBuffer();
for (int i = 0; i < args.length; i++)
{
if (i > 0) sb.append(","); //$NON-NLS-1$
sb.append(args[i]);
}
return sb.toString();
}
}
}