/**
* Copyright (c) 2014 committers of YAKINDU and others.
* 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:
* committers of YAKINDU - initial API and implementation
*
*/
package org.yakindu.base.types.inferrer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.util.PolymorphicDispatcher;
import org.yakindu.base.types.Type;
import org.yakindu.base.types.TypeAlias;
import org.yakindu.base.types.typesystem.ITypeSystem;
import org.yakindu.base.types.validation.IValidationIssueAcceptor;
import org.yakindu.base.types.validation.IValidationIssueAcceptor.ListBasedValidationIssueAcceptor;
import org.yakindu.base.types.validation.IValidationIssueAcceptor.ValidationIssue;
import org.yakindu.base.types.validation.IValidationIssueAcceptor.ValidationIssue.Severity;
import org.yakindu.base.types.validation.TypeValidationError;
import org.yakindu.base.types.validation.TypeValidator;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.inject.Inject;
/**
* @author andreas muelder - Initial contribution and API
*
*/
public abstract class AbstractTypeSystemInferrer implements ITypeSystemInferrer {
protected static final String NO_INFER_METHOD = "No infer method for type(s) %s";
protected static final String ASSERT_IS_TYPE = "Expected one of %s, but was %s.";
public static final String ASSERT_NOT_TYPE = "Expected type is not %s.";
public static final String ASSERT_SAME = "Expected types %s and %s are same.";
public static final String ASSERT_COMPATIBLE = "Incompatible types %s and %s.";
private static final String METHOD_NAME = "doInfer";
@Inject
protected ITypeSystem registry;
@Inject TypeValidator typeValidator;
protected IValidationIssueAcceptor acceptor;
private PolymorphicDispatcher<Object> dispatcher;
private LoadingCache<EObject, InferenceResult> typeCache;
public AbstractTypeSystemInferrer() {
initDispatcher();
}
protected InferenceResult getResultFor(String name) {
return InferenceResult.from(registry.getType(name));
}
protected InferenceResult getCommonType(InferenceResult result1, InferenceResult result2) {
return InferenceResult.from(registry.getCommonType(result1.getType(), result2.getType()));
}
@Override
public final InferenceResult infer(EObject object) {
return infer(object, null);
}
@Override
public final InferenceResult infer(EObject object, IValidationIssueAcceptor acceptor) {
initTypeCache();
this.acceptor = (acceptor != null ? acceptor : new ListBasedValidationIssueAcceptor());
InferenceResult result = inferTypeDispatch(object);
typeCache.invalidateAll();
return result;
}
protected InferenceResult inferTypeDispatch(EObject object) {
if (object == null || object.eIsProxy())
return null;
try {
return typeCache.get(object);
} catch (Exception e) {
// Ignore invalid expressions and recursions
}
return null;
}
private void initTypeCache() {
typeCache = CacheBuilder.newBuilder().maximumSize(100).build(new CacheLoader<EObject, InferenceResult>() {
public InferenceResult load(EObject key) {
// TODO: this is not relevant anymore as we do not declare type
// aliases in type system
if (key instanceof TypeAlias) {
// for type aliases we want to infer their base types
return (InferenceResult) dispatcher.invoke(key);
}
if (key instanceof Type) {
Collection<Type> types = registry.getTypes();
for (Type type : types) {
if (registry.isSame((Type) key, type))
return InferenceResult.from(type);
}
}
return (InferenceResult) dispatcher.invoke(key);
}
});
}
protected void initDispatcher() {
dispatcher = new PolymorphicDispatcher<Object>(METHOD_NAME, 1, 1, Collections.singletonList(this),
new PolymorphicDispatcher.ErrorHandler<Object>() {
@Override
public Object handle(Object[] params, Throwable throwable) {
if (throwable instanceof NoSuchMethodError) {
warning(String.format(NO_INFER_METHOD, Arrays.toString(params)), NO_INFER_METHOD_CODE);
} else {
error(throwable.getMessage(), EXCEPTION_CODE);
}
return null;
}
});
}
protected void assertNotType(InferenceResult currentResult, String msg, InferenceResult... candidates) {
for(TypeValidationError e: typeValidator.assertNotType(currentResult, msg, candidates)) {
error(e);
}
}
protected void assertSame(InferenceResult result1, InferenceResult result2, String msg) {
for(TypeValidationError e: typeValidator.assertSame(result1, result2, msg)) {
error(e);
}
}
protected void assertCompatible(InferenceResult result1, InferenceResult result2, String msg) {
for(TypeValidationError e: typeValidator.assertCompatible(result1, result2, msg)) {
error(e);
}
}
protected void assertAssignable(InferenceResult varResult, InferenceResult valueResult, String msg) {
for(TypeValidationError e: typeValidator.assertAssignable(varResult, valueResult, msg)) {
error(e);
}
}
protected void assertTypeBindingsSame(InferenceResult result1, InferenceResult result2, String msg) {
for(TypeValidationError e: typeValidator.assertTypeBindingsSame(result1, result2, msg)) {
error(e);
}
}
protected void assertIsSubType(InferenceResult subResult, InferenceResult superResult, String msg) {
for(TypeValidationError e: typeValidator.assertIsSubType(subResult, superResult, msg)) {
error(e);
}
}
protected boolean isNullOnComplexType(InferenceResult result1, InferenceResult result2) {
return typeValidator.isNullOnComplexType(result1, result2);
}
protected void info(String msg, String issueCode) {
acceptor.accept(new ValidationIssue(Severity.INFO, msg, issueCode));
}
protected void warning(String msg, String issueCode) {
acceptor.accept(new ValidationIssue(Severity.WARNING, msg, issueCode));
}
protected void error(String msg, String issueCode) {
acceptor.accept(new ValidationIssue(Severity.ERROR, msg, issueCode));
}
protected void error(TypeValidationError e) {
error(e.getMessage(), e.getErrorCode());
}
}