/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.sarl.lang.compiler; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import javax.inject.Singleton; import com.google.inject.Inject; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xtend.core.macro.ConstantExpressionsInterpreter; import org.eclipse.xtend.core.xtend.XtendExecutable; import org.eclipse.xtend.core.xtend.XtendFunction; import org.eclipse.xtext.common.types.JvmAnnotationReference; import org.eclipse.xtext.common.types.JvmAnnotationTarget; import org.eclipse.xtext.common.types.JvmBooleanAnnotationValue; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.common.types.JvmStringAnnotationValue; import org.eclipse.xtext.common.types.JvmType; import org.eclipse.xtext.common.types.JvmTypeAnnotationValue; import org.eclipse.xtext.common.types.JvmTypeReference; import org.eclipse.xtext.common.types.util.TypeReferences; import org.eclipse.xtext.util.PolymorphicDispatcher; import org.eclipse.xtext.util.ReflectionUtil; import org.eclipse.xtext.util.Strings; import org.eclipse.xtext.xbase.XBlockExpression; import org.eclipse.xtext.xbase.XBooleanLiteral; import org.eclipse.xtext.xbase.XCastedExpression; import org.eclipse.xtext.xbase.XExpression; import org.eclipse.xtext.xbase.XInstanceOfExpression; import org.eclipse.xtext.xbase.XNullLiteral; import org.eclipse.xtext.xbase.XNumberLiteral; import org.eclipse.xtext.xbase.XReturnExpression; import org.eclipse.xtext.xbase.XStringLiteral; import org.eclipse.xtext.xbase.XTypeLiteral; import org.eclipse.xtext.xbase.compiler.ImportManager; import org.eclipse.xtext.xbase.compiler.output.FakeTreeAppendable; import org.eclipse.xtext.xbase.jvmmodel.JvmAnnotationReferenceBuilder; import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder; import org.eclipse.xtext.xbase.lib.Inline; import org.eclipse.xtext.xbase.typesystem.util.CommonTypeComputationServices; import io.sarl.lang.generator.GeneratorConfig2; import io.sarl.lang.generator.GeneratorConfigProvider2; /** Compiler for creating inline expressions with Java syntax. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 0.4 * @see Inline */ @Singleton public class JavaInlineExpressionCompiler implements IInlineExpressionCompiler { @Inject private JvmAnnotationReferenceBuilder.Factory annotationRefBuilderFactory; @Inject private CommonTypeComputationServices services; @Inject private JvmTypesBuilder typeBuilder; @Inject private TypeReferences typeReferences; @Inject private ConstantExpressionsInterpreter expressionInterpreter; @Inject private GeneratorConfigProvider2 configProvider; private final PolymorphicDispatcher<Void> generateDispatcher; /** Constructor. */ public JavaInlineExpressionCompiler() { this.generateDispatcher = new PolymorphicDispatcher<Void>( "_generate", 3, 3, //$NON-NLS-1$ Collections.singletonList(this)) { @Override protected Void handleNoSuchMethod(Object... params) { return null; } }; } /** Create an appendable. * * @param imports the import manager. * @return the appendable. */ @SuppressWarnings("static-method") protected InlineAnnotationTreeAppendable newAppendable(ImportManager imports) { return new InlineAnnotationTreeAppendable(imports); } private static XExpression filterSingleOperation(XExpression expression) { XExpression content = expression; while (content instanceof XBlockExpression) { final XBlockExpression blockExpr = (XBlockExpression) content; if (blockExpr.getExpressions().size() == 1) { content = blockExpr.getExpressions().get(0); } else { content = null; } } return content; } @Override public void appendInlineAnnotation(JvmAnnotationTarget target, ResourceSet resourceSet, String inlineExpression, JvmTypeReference... types) { appendInlineAnnotation(target, resourceSet, inlineExpression, false, false, types); } /** Append the inline annotation to the given operation. * * @param target the target of the annotation. * @param resourceSet the resource set that is associated to the given operation. * @param inlineExpression the inline expression. * @param isConstantExpression indicates if the expression is a constant. * @param isStatementExpression indicates if the expression is a statement. * @param types the types to import if the inline expression is used. The references are cloned by this function. */ protected void appendInlineAnnotation(JvmAnnotationTarget target, ResourceSet resourceSet, String inlineExpression, boolean isConstantExpression, boolean isStatementExpression, JvmTypeReference... types) { final JvmAnnotationReferenceBuilder annotationTypesBuilder = this.annotationRefBuilderFactory.create( resourceSet); final JvmAnnotationReference annotationReference = annotationTypesBuilder.annotationRef( Inline.class); final AnnotationInformation annotationInfo = new AnnotationInformation(annotationReference); // Value final JvmStringAnnotationValue annotationValue = this.services.getTypesFactory() .createJvmStringAnnotationValue(); annotationValue.getValues().add(inlineExpression); annotationValue.setOperation(annotationInfo.valueOperation); annotationReference.getExplicitValues().add(annotationValue); // Imported for (final JvmTypeReference type : types) { final JvmTypeAnnotationValue annotationImportedType = this.services.getTypesFactory() .createJvmTypeAnnotationValue(); annotationImportedType.getValues().add(this.typeBuilder.cloneWithProxies(type)); annotationImportedType.setOperation(annotationInfo.importedOperation); annotationReference.getExplicitValues().add(annotationImportedType); } // Constant if (isConstantExpression) { final JvmBooleanAnnotationValue annotationConstant = this.services.getTypesFactory() .createJvmBooleanAnnotationValue(); annotationConstant.getValues().add(Boolean.valueOf(isConstantExpression)); annotationConstant.setOperation(annotationInfo.constantExpressionOperation); annotationReference.getExplicitValues().add(annotationConstant); } // Statement if (isStatementExpression) { final JvmBooleanAnnotationValue annotationStatement = this.services.getTypesFactory() .createJvmBooleanAnnotationValue(); annotationStatement.getValues().add(Boolean.valueOf(isStatementExpression)); annotationStatement.setOperation(annotationInfo.statementExpressionOperation); annotationReference.getExplicitValues().add(annotationStatement); } target.getAnnotations().add(annotationReference); } @Override public void appendInlineAnnotation(JvmAnnotationTarget target, XtendExecutable source) { final ImportManager imports = new ImportManager(); final InlineAnnotationTreeAppendable result = newAppendable(imports); generate(source.getExpression(), source, result); final String content = result.getContent(); if (!Strings.isEmpty(content)) { final List<String> importedTypes = imports.getImports(); final JvmTypeReference[] importArray = new JvmTypeReference[importedTypes.size()]; for (int i = 0; i < importArray.length; ++i) { importArray[i] = this.typeReferences.getTypeForName(importedTypes.get(i), source); } appendInlineAnnotation(target, source.eResource().getResourceSet(), content, result.isConstant(), result.isStatement(), importArray); } } /** Append the inline annotation to the given operation. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the inline code. */ protected void generate(XExpression expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { final XExpression realExpression = filterSingleOperation(expression); if (realExpression != null) { final GeneratorConfig2 config = this.configProvider.get(feature); if (config.isUseExpressionInterpreterForInlineAnnotation() && feature instanceof XtendFunction) { try { final Object evaluationResult = this.expressionInterpreter.evaluate(realExpression, ((XtendFunction) feature).getReturnType()); if (evaluationResult instanceof CharSequence) { output.appendStringConstant(evaluationResult.toString()); } else if (evaluationResult instanceof JvmTypeReference) { output.appendTypeConstant(((JvmTypeReference) evaluationResult).getType()); } else if (evaluationResult instanceof Number) { final Class<?> type = ReflectionUtil.getRawType(evaluationResult.getClass()); if (Byte.class.equals(type) || byte.class.equals(type)) { output.appendConstant("(byte) (" + evaluationResult.toString() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } else if (Short.class.equals(type) || short.class.equals(type)) { output.appendConstant("(short) (" + evaluationResult.toString() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } else if (Float.class.equals(type) || float.class.equals(type)) { output.appendConstant(evaluationResult.toString() + "f"); //$NON-NLS-1$ } else { output.appendConstant(evaluationResult.toString()); } } else { output.appendConstant(Objects.toString(evaluationResult)); } return; } catch (Exception exception) { // Ignore all the exceptions } } this.generateDispatcher.invoke(realExpression, feature, output); } } /** Append the inline code for the given XBooleanLiteral. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ @SuppressWarnings("static-method") protected void _generate(XBooleanLiteral expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { output.appendConstant(Boolean.toString(expression.isIsTrue())); } /** Append the inline code for the given XNullLiteral. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ @SuppressWarnings("static-method") protected void _generate(XNullLiteral expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { output.appendConstant(Objects.toString(null)); } /** Append the inline code for the given XNumberLiteral. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ @SuppressWarnings("static-method") protected void _generate(XNumberLiteral expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { output.appendConstant(expression.getValue()); } /** Append the inline code for the given XStringLiteral. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ @SuppressWarnings("static-method") protected void _generate(XStringLiteral expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { output.appendStringConstant(expression.getValue()); } /** Append the inline code for the given XTypeLiteral. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ @SuppressWarnings("static-method") protected void _generate(XTypeLiteral expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { output.appendTypeConstant(expression.getType()); } /** Append the inline code for the given XCastedExpression. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ protected void _generate(XCastedExpression expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { final InlineAnnotationTreeAppendable child = newAppendable(output.getImportManager()); generate(expression.getTarget(), feature, child); final String childContent = child.getContent(); if (!Strings.isEmpty(childContent)) { output.append("("); //$NON-NLS-1$ output.append(expression.getType().getType()); output.append(")"); //$NON-NLS-1$ output.append(childContent); output.setConstant(child.isConstant()); } } /** Append the inline code for the given XInstanceOfExpression. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ protected void _generate(XInstanceOfExpression expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { final InlineAnnotationTreeAppendable child = newAppendable(output.getImportManager()); generate(expression.getExpression(), feature, child); final String childContent = child.getContent(); if (!Strings.isEmpty(childContent)) { output.append(childContent); output.append(" instanceof "); //$NON-NLS-1$ output.append(expression.getType().getType()); output.setConstant(child.isConstant()); } } /** Append the inline code for the given XReturnLiteral. * * @param expression the expression of the operation. * @param feature the feature that contains the expression. * @param output the output. */ protected void _generate(XReturnExpression expression, XtendExecutable feature, InlineAnnotationTreeAppendable output) { generate(expression.getExpression(), feature, output); } /** * Information about the line annotation. * * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 0.4 */ @SuppressWarnings("checkstyle:visibilitymodifier") private static class AnnotationInformation { public final JvmOperation valueOperation; public final JvmOperation importedOperation; public final JvmOperation constantExpressionOperation; public final JvmOperation statementExpressionOperation; /** Construct. * @param annotationReference annotation reference. */ AnnotationInformation(JvmAnnotationReference annotationReference) { JvmOperation value = null; JvmOperation imported = null; JvmOperation constant = null; JvmOperation statement = null; final Iterator<JvmOperation> operationIterator = annotationReference.getAnnotation() .getDeclaredOperations().iterator(); while ((value == null || imported == null || constant == null || statement == null) && operationIterator.hasNext()) { final JvmOperation annotationOperation = operationIterator.next(); if (annotationOperation.getSimpleName().equals("value")) { //$NON-NLS-1$ value = annotationOperation; } else if (annotationOperation.getSimpleName().equals("imported")) { //$NON-NLS-1$ imported = annotationOperation; } else if (annotationOperation.getSimpleName().equals("constantExpression")) { //$NON-NLS-1$ constant = annotationOperation; } else if (annotationOperation.getSimpleName().equals("statementExpression")) { //$NON-NLS-1$ statement = annotationOperation; } } assert value != null; assert imported != null; assert constant != null; assert statement != null; this.valueOperation = value; this.importedOperation = imported; this.constantExpressionOperation = constant; this.statementExpressionOperation = statement; } } /** * Appendable for creating an inline expression. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 0.4 */ @SuppressWarnings("checkstyle:visibilitymodifier") protected static class InlineAnnotationTreeAppendable extends FakeTreeAppendable { private boolean isConstant; private boolean isStatement; /** Constructor. * * @param imports the manager of imports. */ public InlineAnnotationTreeAppendable(ImportManager imports) { super(imports); } @Override public ImportManager getImportManager() { // Change the visibility. return super.getImportManager(); } /** Replies if the expression is constant. * * @return is constant. */ public boolean isConstant() { return this.isConstant; } /** Replies if the expression is statement. * * @return is statement. */ public boolean isStatement() { return this.isStatement; } /** Change the constant flag. * * @param isConstant is a constant. */ public void setConstant(boolean isConstant) { this.isConstant = isConstant; } /** Change the statement flag. * * @param isStatement is a statement. */ public void setStatement(boolean isStatement) { this.isStatement = isStatement; } /** Append a constant. * * @param constant the constant. */ public void appendConstant(String constant) { append(constant); this.isConstant = true; } /** Append a type constant. * * @param type the type. */ public void appendTypeConstant(JvmType type) { append(type); append(".class"); //$NON-NLS-1$ setConstant(true); } /** Append a string constant. * * @param stringValue the value of the string. */ public void appendStringConstant(String stringValue) { appendConstant("\"" //$NON-NLS-1$ + org.eclipse.xtext.util.Strings.convertToJavaString(stringValue) + "\""); //$NON-NLS-1$ } } }