/*
* Copyright (c) 2012-2015, Microsoft Mobile
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.juniversal.translator.csharp;
import org.eclipse.jdt.core.dom.*;
import org.jetbrains.annotations.Nullable;
import org.juniversal.translator.core.JUniversalException;
import java.util.ArrayList;
import java.util.List;
import static org.juniversal.translator.core.ASTUtil.*;
public abstract class MethodInvocationWriterBase<T extends Expression> extends CSharpASTNodeWriter<T> {
public MethodInvocationWriterBase(CSharpSourceFileWriter cSharpASTWriters) {
super(cSharpASTWriters);
}
protected void writeMethodInvocation(T methodInvocationNode, @Nullable Expression expression, SimpleName methodName,
List typeArguments, List arguments, IMethodBinding methodBinding) {
String methodNameString = methodName.getIdentifier();
ArrayList<Expression> args = new ArrayList<>();
for (Object argument : arguments)
args.add((Expression) argument);
ITypeBinding objectType;
if (expression != null)
objectType = expression.resolveTypeBinding();
else objectType = methodBinding.getDeclaringClass();
//TODO: Detect when precedence allows skkpping parens
boolean addParentheses = false;
if (isType(objectType, "java.lang.String") && methodNameString.equals("isEmpty"))
addParentheses = true;
if (addParentheses)
write("(");
if (expression != null) {
writeNode(expression);
copySpaceAndComments();
// If the Java method is mapped to an overloaded operator in C# (e.g. charAt -> []) then handle that case
// here, with no "." getting written
if (writeMethodMappedToOperatorOverload(methodNameString, args, methodBinding))
return;
// A functional interface method becomes a delegate. So in Java a call of the form
// "funcInterface.funcMethod(...)" becomes in C# "funcInterface(...)". Handle that case here
@Nullable ITypeBinding expressionTypeBinding = expression.resolveTypeBinding();
if (expressionTypeBinding != null && isFunctionalInterface(expressionTypeBinding)) {
match(".");
skipSpaceAndComments();
match(methodNameString);
skipSpaceAndComments();
writeMethodInvocationArgumentList(arguments);
return;
}
matchAndWrite(".");
copySpaceAndComments();
}
// If it's a standard Object method (toString, equals, etc.), handle that first
if (writeMappedObjectMethod(methodInvocationNode, methodNameString, args, methodBinding))
;
else if (implementsInterface(objectType, "java.lang.List") &&
writeMappedListMethod(methodInvocationNode, methodNameString, args, methodBinding))
;
else if (isType(objectType, "java.lang.String"))
writeMappedStringMethod(methodInvocationNode, methodNameString, args, methodBinding);
else if (isType(objectType, "java.lang.StringBuilder"))
writeMappedStringBuilderMethod(methodInvocationNode, methodNameString, args, methodBinding);
else {
// In C# type arguments for methods come after the method name, not before ("foo.<String>bar(3)" in Java is
// "foo.bar<String>(3)" in C#). So change the order around here.
if (typeArguments != null && !typeArguments.isEmpty()) {
writeNodeAtDifferentPosition(methodName);
matchAndWrite("<");
writeCommaDelimitedNodes(typeArguments, (Type type) -> {
copySpaceAndComments();
writeNode(type);
});
copySpaceAndComments();
matchAndWrite(">");
setPositionToEndOfNode(methodName);
} else matchAndWrite(methodNameString);
copySpaceAndComments();
writeMethodInvocationArgumentList(arguments);
}
if (addParentheses)
write(")");
}
/**
* Some Java methods are mapped to overloaded operators in C# (just String.charAt -> [] currently). Handle those
* here.
*
* @param methodName method name
* @param args method arguments
* @param methodBinding IMethodBinding object
* @return true iff the method call was handled, mapped to an operator
*/
private boolean writeMethodMappedToOperatorOverload(String methodName, ArrayList<Expression> args,
IMethodBinding methodBinding) {
if (isStatic(methodBinding))
return false;
ITypeBinding objectType = methodBinding.getDeclaringClass();
if (isType(objectType, "java.lang.String") || isType(objectType, "java.lang.AbstractStringBuilder")) {
switch (methodName) {
case "charAt":
verifyArgCount(args, 1);
match(".");
copySpaceAndComments();
match("charAt");
copySpaceAndComments();
matchAndWrite("(", "[");
copySpaceAndComments();
writeNode(args.get(0));
copySpaceAndComments();
matchAndWrite(")", "]");
return true;
default:
return false;
}
} else return false;
}
private boolean writeMappedObjectMethod(T methodInvocation, String methodName,
ArrayList<Expression> args, IMethodBinding methodBinding) {
if (isStatic(methodBinding))
return false;
switch (methodName) {
case "equals":
match(methodName);
verifyArgCount(args, 1);
writeMappedMethod("Equals", args.get(0));
setPositionToEndOfNode(methodInvocation);
return true;
case "hashCode":
match(methodName);
verifyArgCount(args, 0);
writeMappedMethod("GetHashCode");
setPositionToEndOfNode(methodInvocation);
return true;
case "toString":
match(methodName);
verifyArgCount(args, 0);
writeMappedMethod("ToString");
setPositionToEndOfNode(methodInvocation);
return true;
default:
return false;
}
}
private boolean writeMappedListMethod(T methodInvocation, String methodName,
ArrayList<Expression> args, IMethodBinding methodBinding) {
if (isStatic(methodBinding))
return false;
switch (methodName) {
case "add":
match(methodName);
if (args.size() == 1)
writeMappedMethod("Equals", args.get(0));
setPositionToEndOfNode(methodInvocation);
return true;
default:
return false;
}
}
private void writeMappedStringMethod(T methodInvocation, String methodName,
ArrayList<Expression> args, IMethodBinding methodBinding) {
// Skip past the method name; we'll write a different name instead
match(methodName);
if (isStatic(methodBinding)) {
switch (methodName) {
default:
throw sourceNotSupported("Java static method String." + methodName + " isn't supported by the translator");
}
} else {
switch (methodName) {
case "compareTo":
verifyArgCount(args, 1);
writeMappedMethod("CompareTo", args.get(0));
break;
case "contains":
verifyArgCount(args, 1);
writeMappedMethod("Contains", args.get(0));
break;
case "endsWith":
verifyArgCount(args, 1);
writeMappedMethod("EndsWith", args.get(0), nativeReference("System", "StringComparison.Ordinal"));
break;
case "indexOf":
verifyArgCount(args, 1, 2);
if (args.size() == 1)
writeMappedMethod("IndexOf", args.get(0));
else writeMappedMethod("IndexOf", args.get(0), args.get(1));
break;
case "lastIndexOf":
verifyArgCount(args, 1, 2);
if (args.size() == 1)
writeMappedMethod("LastIndexOf", args.get(0));
else writeMappedMethod("LastIndexOf", args.get(0), args.get(1));
break;
// TODO: Handle adding parens when needed
case "isEmpty":
verifyArgCount(args, 0);
write("Length == 0");
break;
case "length":
verifyArgCount(args, 0);
write("Length");
break;
case "replace":
verifyArgCount(args, 2);
writeMappedMethod("Replace", args.get(0), args.get(1));
break;
case "startsWith":
verifyArgCount(args, 1);
writeMappedMethod("StartsWith", args.get(0), nativeReference("System", "StringComparison.Ordinal"));
break;
case "substring":
verifyArgCount(args, 1, 2);
if (args.size() == 1)
writeMappedMethod("Substring", args.get(0));
else {
Expression arg0 = args.get(0);
Expression arg1 = args.get(1);
// TODO: Verify that arg0 and arg1 and just identifiers or constants, to keep things simple
write("Substring");
write("(");
setPositionToStartOfNode(arg0);
writeNode(arg0);
write(", ");
setPositionToStartOfNode(arg1);
writeNode(arg1);
write(" - ");
setPositionToStartOfNode(arg0);
writeNode(arg0);
write(")");
}
break;
// TODO: Remove this
case "split":
verifyArgCount(args, 1, 1);
writeMappedMethod("xxxxx");
break;
case "toCharArray":
verifyArgCount(args, 0);
writeMappedMethod("ToCharArray");
break;
case "trim":
verifyArgCount(args, 0);
writeMappedMethod("Trim");
break;
default:
throw sourceNotSupported("Java method String." + methodName + " isn't supported by the translator");
}
}
setPositionToEndOfNode(methodInvocation);
}
private void writeMappedStringBuilderMethod(T methodInvocation, String methodName,
ArrayList<Expression> args, IMethodBinding methodBinding) {
if (isStatic(methodBinding))
throw sourceNotSupported("Java static method StringBuilder." + methodName + " isn't supported by the translator");
// Skip past the method name; we'll write a different name instead
match(methodName);
switch (methodName) {
case "append":
verifyArgCount(args, 1, 3);
if (args.size() == 1)
writeMappedMethod("Append", args.get(0));
else writeMappedMethod("Append", args.get(0), args.get(1), args.get(2));
break;
case "length":
verifyArgCount(args, 0);
write("Length");
break;
case "deleteCharAt":
verifyArgCount(args, 1);
writeMappedMethod("Remove", args.get(0), "1");
break;
case "toString":
verifyArgCount(args, 0);
writeMappedMethod("ToString");
break;
default:
throw sourceNotSupported("Java method StringBuilder." + methodName + " isn't supported by the translator");
}
setPositionToEndOfNode(methodInvocation);
}
private void verifyArgCount(ArrayList<Expression> args, int expectedArgCount) {
if (args.size() != expectedArgCount)
throw sourceNotSupported("Method call has " + args.size() +
" argument(s); the translator only supports this method with " +
expectedArgCount + " argument(s)");
}
private void verifyArgCount(ArrayList<Expression> args, int expectedArgCount1, int expectedArgCount2) {
if (args.size() != expectedArgCount1 && args.size() != expectedArgCount2)
throw sourceNotSupported("Method call has " + args.size() +
" argument(s); the translator only supports this method with " +
expectedArgCount1 + " or " + expectedArgCount2 + " argument(s)");
}
private void writeMappedMethod(String mappedMethodName, Object... args) {
write(mappedMethodName);
write("(");
boolean first = true;
for (Object arg : args) {
if (!first)
write(", ");
if (arg instanceof Expression) {
Expression expressionArg = (Expression) arg;
setPositionToStartOfNode(expressionArg);
writeNode(expressionArg);
} else if (arg instanceof String)
write((String) arg);
else throw new JUniversalException("Unexpected arg object type in writeMappedMethod");
first = false;
}
write(")");
}
}