| /******************************************************************************* |
| * 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.BasicDiagnostic; |
| import org.eclipse.emf.common.util.Diagnostic; |
| import org.eclipse.jdt.core.Signature; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * @param firstType |
| * @param secondType |
| * @return true if firstType is assignable to secondType or vice-versa, |
| * depending on their assignment and runtime types |
| */ |
| public static 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 static SignatureTestResult checkTypeSignature( |
| final String isSignature, final List<String> mustbeTypes, |
| final List<String> mustbeMethods, final boolean mustbeWriteable) { |
| if (mustbeTypes.isEmpty()) { |
| final Diagnostic diag = new BasicDiagnostic( |
| Diagnostic.ERROR, |
| "", 0, Messages.getString("TypeComparator.Expression.No_Method"), null); //$NON-NLS-1$ //$NON-NLS-2$ |
| 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 = new BasicDiagnostic( |
| Diagnostic.WARNING, |
| "", 0, NLS.bind(Messages.getString("TypeComparator.Expression.Incompatible_Value"), params), null); //$NON-NLS-1$ //$NON-NLS-2$ |
| return new SignatureTestResult(diag, 1); |
| } |
| |
| private static SignatureTestResult checkMethodSignature( |
| final String isSignature, final List<String> mustbeTypes, |
| final List<String> mustbeMethods) { |
| if (mustbeMethods.isEmpty()) { |
| final Diagnostic diag = new BasicDiagnostic( |
| Diagnostic.ERROR, |
| "", 0, Messages.getString("TypeComparator.Expression.No_Value"), null); //$NON-NLS-1$ //$NON-NLS-2$ |
| 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 = new BasicDiagnostic( |
| Diagnostic.ERROR, |
| "", 0, NLS.bind(Messages.getString("TypeComparator.Expression.Incompatible_Method"), params), null); //$NON-NLS-1$ //$NON-NLS-2$ |
| 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 |
| if (TypeCoercer.typeIsString(checkType)) { |
| // if check type expects writability, need to ensure that |
| // coercability is reversible |
| 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 static Diagnostic checkAssignability(final CompositeType firstType, |
| final CompositeType secondType) { |
| if (firstType.isRHS() && !secondType.isRHS()) { |
| return new BasicDiagnostic( |
| Diagnostic.WARNING, |
| "", 0, //$NON-NLS-1$ |
| Messages |
| .getString("TypeComparator.Expression.Not.Gettable"), null); //$NON-NLS-1$ |
| } |
| |
| if (firstType.isLHS() && !secondType.isLHS()) { |
| return new BasicDiagnostic( |
| Diagnostic.WARNING, |
| "", 0, //$NON-NLS-1$ |
| Messages |
| .getString("TypeComparator.Expression.Expected.Settable"), null); //$NON-NLS-1$ |
| } |
| |
| return Diagnostic.OK_INSTANCE; |
| } |
| |
| private TypeComparator() { |
| // static utility class; not instantiable |
| } |
| } |