/******************************************************************************* * 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; } }