blob: a57a94bede0e0c9a76f772f275f7c83476960748 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Oracle Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cameron Bateman/Oracle - initial API and implementation
*
********************************************************************************/
package org.eclipse.jst.jsf.common.internal.types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.jdt.core.Signature;
/**
* Static utility class used to compare two CompositeTypes for compatability
*
* @author cbateman
*
*/
public final class TypeComparator {
private static class SignatureTestResult {
/**
* the diagnostic
*/
private final Diagnostic diagnostic;
/**
* Measure of the probability that the tested signatures were meant to
* match. Larger value means higher probability.
*/
private final int matchQuality;
/**
* @param diagnostic
* @param matchQuality -
* Measure of the probability that the tested signatures were
* meant to match. Larger value means higher probability.
*/
public SignatureTestResult(final Diagnostic diagnostic,
final int matchQuality) {
super();
this.diagnostic = diagnostic;
this.matchQuality = matchQuality;
}
}
private final TypeComparatorDiagnosticFactory _factory;
/**
* Default Constructor
* @param factory
*/
public TypeComparator(final TypeComparatorDiagnosticFactory factory)
{
_factory = factory;
}
/**
* @param firstType
* @param secondType
* @return true if firstType is assignable to secondType or vice-versa,
* depending on their assignment and runtime types
*/
public Diagnostic calculateTypeCompatibility(
final CompositeType firstType, final CompositeType secondType) {
// first, box all primitives
final CompositeType boxedFirstType = TypeTransformer
.transformBoxPrimitives(firstType);
final CompositeType boxedSecondType = TypeTransformer
.transformBoxPrimitives(secondType);
final String[] mustBeSatisfied = boxedFirstType.getSignatures();
final String[] testSignatures = boxedSecondType.getSignatures();
List<String> mustbeMethods = Collections.emptyList();
List<String> mustbeTypes = Collections.emptyList();
for (final String mustbeSignature : mustBeSatisfied) {
if (TypeUtil.isMethodSignature(mustbeSignature)) {
if (mustbeMethods.isEmpty()) {
mustbeMethods = new ArrayList<String>(mustbeSignature
.length());
}
mustbeMethods.add(mustbeSignature);
} else {
if (mustbeTypes.isEmpty()) {
mustbeTypes = new ArrayList<String>(mustbeSignature
.length());
}
mustbeTypes.add(mustbeSignature);
}
}
final boolean mustbeWriteable = firstType.isLHS();
SignatureTestResult bestResult = null;
for (final String isSignature : testSignatures) {
SignatureTestResult testResult;
if (TypeUtil.isMethodSignature(isSignature)) {
testResult = checkMethodSignature(isSignature, mustbeTypes,
mustbeMethods);
if (testResult.diagnostic.getSeverity() == Diagnostic.OK) {
return testResult.diagnostic;
}
} else {
testResult = checkTypeSignature(isSignature, mustbeTypes,
mustbeMethods, mustbeWriteable);
if (testResult.diagnostic.getSeverity() == Diagnostic.OK) {
return checkAssignability(firstType, secondType);
}
}
if (bestResult == null
|| bestResult.matchQuality < testResult.matchQuality) {
bestResult = testResult;
}
}
// TODO: bestResult empty? (should not happen, but who knows...
return bestResult.diagnostic;
}
private SignatureTestResult checkTypeSignature(
final String isSignature, final List<String> mustbeTypes,
final List<String> mustbeMethods, final boolean mustbeWriteable) {
if (mustbeTypes.isEmpty()) {
final Diagnostic diag = _factory.create_METHOD_EXPRESSION_EXPECTED();
return new SignatureTestResult(diag, 0);
}
for (final String mustbeSignature : mustbeTypes) {
if (mustbeSignature.equals(isSignature)
|| canCoerce(isSignature, mustbeSignature, mustbeWriteable)) {
final Diagnostic diag = Diagnostic.OK_INSTANCE;
return new SignatureTestResult(diag, 5);
}
}
final String[] params = new String[2];
params[0] = readableSignatures(mustbeTypes);
params[1] = Signature.toString(isSignature);
final Diagnostic diag = _factory.create_INCOMPATIBLE_TYPES(params);
return new SignatureTestResult(diag, 1);
}
private SignatureTestResult checkMethodSignature(
final String isSignature, final List<String> mustbeTypes,
final List<String> mustbeMethods) {
if (mustbeMethods.isEmpty()) {
final Diagnostic diag = _factory.create_VALUE_EXPRESSION_EXPECTED();
return new SignatureTestResult(diag, 0);
}
for (final String mustbeSignature : mustbeMethods) {
if (methodSignaturesMatch(mustbeSignature, isSignature)) {
final Diagnostic diag = Diagnostic.OK_INSTANCE;
return new SignatureTestResult(diag, 5);
}
}
final String[] params = new String[2];
params[0] = readableSignatures(mustbeMethods);
params[1] = Signature
.toString(isSignature, "method", null, false, true); //$NON-NLS-1$
final Diagnostic diag = _factory.create_INCOMPATIBLE_METHOD_TYPES(params);
return new SignatureTestResult(diag, 1);
}
private static String readableSignatures(final List<String> signatures) {
StringBuilder res = null;
for (final String sig : signatures) {
String sigText;
if (TypeUtil.isMethodSignature(sig)) {
sigText = Signature.toString(sig, "method", null, false, true); //$NON-NLS-1$
} else {
sigText = Signature.toString(sig);
}
if (res == null) {
res = new StringBuilder(sigText);
} else {
res.append(", ").append(sigText); //$NON-NLS-1$
}
}
return res != null ? res.toString() : "[no signature]"; //$NON-NLS-1$
}
private static boolean canCoerce(final String testType,
final String checkType, final boolean checkTypeIsWritable) {
boolean canCoerce = canCoerce(testType, checkType);
// if the check type is writable, we need to be sure that the
// coercion can work in both directions
if (canCoerce && checkTypeIsWritable) {
// reverse roles: can checkType assign back to test type?
canCoerce &= canCoerce(checkType, testType);
}
return canCoerce;
}
private static boolean canCoerce(final String testType,
final String checkType) {
// can always to coerce to string or object
if (TypeCoercer.typeIsString(checkType)/*
|| TypeConstants.TYPE_JAVAOBJECT.equals(checkType)*/)
{
return true;
} else if (TypeCoercer.typeIsNumeric(checkType)) {
return canCoerceNumeric(testType);
} else if (TypeCoercer.typeIsBoolean(checkType)) {
return TypeCoercer.canCoerceToBoolean(testType);
}
// otherwise, no type coercion available
return false;
}
private static boolean canCoerceNumeric(final String testType) {
try {
TypeCoercer.coerceToNumber(testType);
// TODO: there is a case when coerceToNumber returns
// null meaning "not sure", that we may want to handle
// differently, with a warning
return true;
} catch (final TypeCoercionException tce) {
// outright failure -- can't coerce
return false;
}
}
private static boolean methodSignaturesMatch(final String firstMethodSig,
final String secondMethodSig) {
// TODO: need to account for primitive type coercions
if (firstMethodSig.equals(secondMethodSig)) {
return true;
}
final String[] firstMethodParams = Signature
.getParameterTypes(firstMethodSig);
final String[] secondMethodParams = Signature
.getParameterTypes(secondMethodSig);
// fail fast if param count doesn't match
if (firstMethodParams.length != secondMethodParams.length) {
return false;
}
// now check each parameter
for (int i = 0; i < firstMethodParams.length; i++) {
// need to box primitives before comparing
final String firstMethodParam = TypeTransformer
.transformBoxPrimitives(firstMethodParams[i]);
final String secondMethodParam = TypeTransformer
.transformBoxPrimitives(secondMethodParams[i]);
if (!firstMethodParam.equals(secondMethodParam)) {
return false;
}
}
// if we get to here then we need only check the return type
final String firstReturn = TypeTransformer
.transformBoxPrimitives(Signature.getReturnType(firstMethodSig));
final String secondReturn = TypeTransformer
.transformBoxPrimitives(Signature
.getReturnType(secondMethodSig));
if (!firstReturn.equals(secondReturn)) {
return false;
}
// if we get to here, then everything checks out
return true;
}
/**
* Precond: both firstType and secondType must represent value bindings.
*
* @param firstType
* @param secondType
* @return a diagnostic validating that the two composite have compatible
* assignability
*/
private Diagnostic checkAssignability(final CompositeType firstType,
final CompositeType secondType) {
if (firstType.isRHS() && !secondType.isRHS()) {
return _factory.create_PROPERTY_NOT_READABLE();
}
if (firstType.isLHS() && !secondType.isLHS()) {
return _factory.create_PROPERTY_NOT_WRITABLE();
}
return Diagnostic.OK_INSTANCE;
}
}