/** * Copyright (c) 2008 Borland Software Corp. * * 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: * Alexander Shatalin (Borland) - initial API and implementation */ package org.eclipse.gmf.internal.xpand.migration; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.gmf.internal.xpand.BuiltinMetaModel; import org.eclipse.gmf.internal.xpand.BuiltinMetaModelExt; import org.eclipse.gmf.internal.xpand.ResourceManager; import org.eclipse.gmf.internal.xpand.expression.AnalysationIssue; import org.eclipse.gmf.internal.xpand.expression.EvaluationException; import org.eclipse.gmf.internal.xpand.expression.SyntaxConstants; import org.eclipse.gmf.internal.xpand.migration.MigrationException.Type; import org.eclipse.gmf.internal.xpand.util.CompositeXtendResource; import org.eclipse.gmf.internal.xpand.xtend.ast.CreateExtensionStatement; import org.eclipse.gmf.internal.xpand.xtend.ast.ExpressionExtensionStatement; import org.eclipse.gmf.internal.xpand.xtend.ast.Extension; import org.eclipse.gmf.internal.xpand.xtend.ast.ExtensionFile; import org.eclipse.gmf.internal.xpand.xtend.ast.JavaExtensionStatement; import org.eclipse.gmf.internal.xpand.xtend.ast.WorkflowSlotExtensionStatement; import org.eclipse.gmf.internal.xpand.xtend.ast.XtendResource; public class XtendMigrationFacade { private static final String JAVA_ARRAY_TYPE_SUFFIX = ".List"; private static final String JAVA_LANG_PACKAGE_PREFIX = "java.lang."; private ResourceManager resourceManager; private StringBuilder output = new StringBuilder(); private String resourceName; private StandardLibraryImports stdLibImportsManager; private boolean injectUnusedImports; private MigrationExecutionContext rootExecutionContext; private TypeManager typeManager; private ModeltypeImports modeltypeImportsManger; private ModelManager modelManager; private List<JavaExtensionDescriptor> javaExtensionDescriptors = new ArrayList<JavaExtensionDescriptor>(); private List<String> importedMetamodels = new ArrayList<String>(); private String nativeLibraryClassName; private String nativeLibraryPackageName = ""; private OclKeywordManager oclKeywordManager; private static String getLastSegment(String string, String separator) { int delimeterIndex = string.lastIndexOf(separator); if (delimeterIndex > 0) { return string.substring(delimeterIndex + separator.length()); } else { return string; } } public XtendMigrationFacade(ResourceManager resourceManager, String xtendResourceName, boolean injectUnusedImports) { this(resourceManager, xtendResourceName); this.injectUnusedImports = injectUnusedImports; } public XtendMigrationFacade(ResourceManager resourceManager, String xtendResourceName, MigrationExecutionContext executionContext) { this(resourceManager, xtendResourceName); rootExecutionContext = executionContext; } public XtendMigrationFacade(ResourceManager resourceManager, String xtendResourceName) { this.resourceManager = resourceManager; this.resourceName = xtendResourceName; } public StringBuilder migrateXtendResource() throws MigrationException { XtendResource xtendResource = resourceManager.loadXtendResource(resourceName); if (xtendResource == null) { throw new MigrationException(Type.RESOURCE_NOT_FOUND, resourceName, "Unable to load resource: " + resourceName); } MigrationExecutionContext ctx = (rootExecutionContext != null ? rootExecutionContext : new MigrationExecutionContextImpl(resourceManager)).cloneWithResource(xtendResource); Set<AnalysationIssue> issues = new HashSet<AnalysationIssue>(); xtendResource.analyze(ctx, issues); if (MigrationException.hasErrors(issues)) { throw new MigrationException(issues, resourceName); } ExtensionFile extensionFile = getFirstExtensionFile(xtendResource); String shortResourceName = getLastSegment(resourceName, SyntaxConstants.NS_DELIM); if (shortResourceName.length() == 0) { throw new MigrationException(Type.INCORRECT_RESOURCE_NAME, resourceName, resourceName); } stdLibImportsManager = new StandardLibraryImports(output); oclKeywordManager = new OclKeywordManager(); modelManager = new ModelManager(stdLibImportsManager, oclKeywordManager); addLibraryImports(extensionFile, false); if (extensionFile.getImportedExtensions().length > 0) { writeln(""); } modeltypeImportsManger = new ModeltypeImports(output, injectUnusedImports, oclKeywordManager); for (String namespace : extensionFile.getImportedNamespaces()) { modeltypeImportsManger.registerModeltype(namespace); importedMetamodels.add(namespace); } typeManager = new TypeManager(modeltypeImportsManger, oclKeywordManager); writeln("library " + shortResourceName + ";"); writeln(""); for (Iterator<Extension> it = extensionFile.getExtensions().iterator(); it.hasNext();) { Extension extension = it.next(); migrateExtension(extension, ctx); if (it.hasNext()) { writeln(""); } } injectModeltypeImports(); injectStdlibImports(); return output; } private ExtensionFile getFirstExtensionFile(XtendResource xtendResource) throws MigrationException { // TODO: there should be more generic way to get first definition.. while (xtendResource instanceof CompositeXtendResource) { xtendResource = ((CompositeXtendResource) xtendResource).getFirstDefinition(); } if (false == xtendResource instanceof ExtensionFile) { throw new MigrationException(Type.UNSUPPORTED_XTEND_RESOURCE, resourceName, "Only ExtensionFile instances are supported, but loaded: " + xtendResource); } return (ExtensionFile) xtendResource; } /** * This method should be executed only after migrateXtendResource() one */ public StringBuilder getNativeLibraryXmlDeclaration() { if (javaExtensionDescriptors.size() == 0) { return null; } StringBuilder result = new StringBuilder(); result.append("<library class=\""); result.append(getNativeLibraryFullClassName()); result.append("\">"); for (String metamodel : importedMetamodels) { result.append("<metamodel nsURI=\""); result.append(metamodel); result.append("\"/>"); } result.append("</library>"); result.append(ExpressionMigrationFacade.LF); return result; } private String getNativeLibraryFullClassName() { return getNativeLibraryPackageName().length() == 0 ? getNativeLibraryClassName() : getNativeLibraryPackageName() + JavaCs.DOT + getNativeLibraryClassName(); } /** * This method should be executed only after migrateXtendResource() one * @throws MigrationException */ public StringBuilder getNativeLibraryClassBody() throws MigrationException { if (javaExtensionDescriptors.size() == 0) { return null; } String lf = ExpressionMigrationFacade.LF; StringBuilder result = new StringBuilder(); if (getNativeLibraryPackageName().length() > 0) { result.append("package "); result.append(getNativeLibraryPackageName()); result.append(";"); result.append(lf); } result.append("import org.eclipse.m2m.qvt.oml.blackbox.java.Operation;"); result.append(lf); result.append("import org.eclipse.m2m.qvt.oml.blackbox.java.Operation.Kind;"); result.append(lf); result.append("public class "); result.append(getNativeLibraryClassName()); result.append(" {"); result.append(lf); for (JavaExtensionDescriptor descriptor : javaExtensionDescriptors) { addNativeMethod(descriptor, result); result.append(lf); } result.append("}"); return result; } private void addNativeMethod(JavaExtensionDescriptor descriptor, StringBuilder result) throws MigrationException { result.append("@Operation(contextual = "); result.append(!descriptor.isStaticQvtoCall()); result.append(", kind = Kind.HELPER)"); result.append(ExpressionMigrationFacade.LF); result.append("public "); if (descriptor.isStaticQvtoCall()) { result.append("static "); } EClassifier extensionReturnType = descriptor.getReturnType(); result.append(getJavaType(extensionReturnType)); result.append(" "); addNativeMethodSignature(descriptor, result); result.append(" { return "); if (BuiltinMetaModel.isParameterizedType(extensionReturnType)) { result.append("org.eclipse.ocl.util.CollectionUtil.<"); result.append(getJavaType(BuiltinMetaModel.getInnerType(extensionReturnType))); result.append("> "); if (BuiltinMetaModelExt.isSetType(extensionReturnType)) { result.append("createNewSet("); } else if (BuiltinMetaModelExt.isListType(extensionReturnType)) { result.append("createNewSequence("); } else { // Collection result.append("createNewBag("); } } result.append(descriptor.getClassName()); result.append(JavaCs.DOT); result.append(descriptor.getMethodName()); result.append("("); List<String> parameterNames = descriptor.getParameterNames(); List<String> javaParameterTypes = descriptor.getJavaParameterTypes(); for (int i = 0; i < parameterNames.size(); i++) { if (i > 0) { result.append(", "); } result.append(parameterNames.get(i)); String javaParameterType = javaParameterTypes.get(i); if (javaParameterType.endsWith(JAVA_ARRAY_TYPE_SUFFIX)) { javaParameterType = javaParameterType.substring(0, javaParameterType.length() - JAVA_ARRAY_TYPE_SUFFIX.length()); result.append(".toArray(new "); result.append(suppressJavaLang(javaParameterType)); result.append("["); result.append(parameterNames.get(i)); result.append(".size()]"); result.append(")"); } } result.append(")"); if (BuiltinMetaModel.isParameterizedType(extensionReturnType)) { result.append(")"); } result.append("; }"); } private String getJavaType(EClassifier xpandType) throws MigrationException { if (xpandType == BuiltinMetaModel.VOID) { throw new MigrationException(Type.UNSUPPORTED_NATIVE_EXTENSION_TYPE, resourceName, "Void type is not supported for native extensions"); } if (xpandType == EcorePackage.eINSTANCE.getEBoolean()) { return "Boolean"; } if (xpandType.getInstanceClassName() != null) { String instanceClassName = xpandType.getInstanceClassName(); return suppressJavaLang(instanceClassName); } if (BuiltinMetaModel.isParameterizedType(xpandType)) { EClassifier innerType = BuiltinMetaModel.getInnerType(xpandType); String innerJavaType = getJavaType(innerType); if (BuiltinMetaModelExt.isSetType(xpandType)) { return "java.util.Set<" + innerJavaType + ">"; } else if (BuiltinMetaModelExt.isListType(xpandType)) { return "java.util.List<" + innerJavaType + ">"; } else if (BuiltinMetaModelExt.isCollectionType(xpandType)) { return "java.util.Collection<" + innerJavaType + ">"; } } return "/*Write proper FQName manually:*/" + xpandType.getName(); } private String suppressJavaLang(String instanceClassName) { // Suppressing "java.lang" package. if (instanceClassName.startsWith(JAVA_LANG_PACKAGE_PREFIX)) { String simpleClassName = instanceClassName.substring(JAVA_LANG_PACKAGE_PREFIX.length()); if (simpleClassName.indexOf(JavaCs.DOT) == -1) { return simpleClassName; } } return instanceClassName; } private void addNativeMethodSignature(JavaExtensionDescriptor descriptor, StringBuilder result) throws MigrationException { result.append(descriptor.getExtensionName()); result.append("("); List<EClassifier> parameterTypes = descriptor.getParameterTypes(); List<String> parameterNames = descriptor.getParameterNames(); assert parameterTypes.size() == parameterNames.size(); for (int i = 0; i < parameterTypes.size(); i++) { if (i > 0) { result.append(", "); } result.append(getJavaType(parameterTypes.get(i))); result.append(" "); result.append(parameterNames.get(i)); } result.append(")"); } public String getNativeLibraryClassName() { return nativeLibraryClassName; } public String getNativeLibraryPackageName() { return nativeLibraryPackageName; } private void injectStdlibImports() { StringBuilder sb = new StringBuilder(); for (String libraryName : stdLibImportsManager.getLibraries()) { sb.append("import "); sb.append(libraryName.replaceAll(SyntaxConstants.NS_DELIM, OclCs.NAMESPACE_SEPARATOR)); sb.append(";"); sb.append(ExpressionMigrationFacade.LF); } if (sb.length() > 0) { sb.append(ExpressionMigrationFacade.LF); write(sb, stdLibImportsManager.getPlaceholderIndex()); } } private void injectModeltypeImports() { StringBuilder sb = new StringBuilder(); for (Entry<String, String> entry : modeltypeImportsManger.getModelTypes().entrySet()) { sb.append("modeltype "); sb.append(entry.getValue()); sb.append(" uses \""); sb.append(entry.getKey()); sb.append("\";"); sb.append(ExpressionMigrationFacade.LF); } if (sb.length() > 0) { sb.append(ExpressionMigrationFacade.LF); write(sb, modeltypeImportsManger.getPlaceholderIndex()); } } private void addLibraryImports(XtendResource xtendResource, boolean reexportedOnly) throws MigrationException { for (String extension : xtendResource.getImportedExtensions()) { if (!reexportedOnly || xtendResource.isReexported(extension)) { writeln("import " + extension.replaceAll(SyntaxConstants.NS_DELIM, OclCs.NAMESPACE_SEPARATOR) + ";"); XtendResource referencedResource = resourceManager.loadXtendResource(extension); if (referencedResource == null) { throw new MigrationException(Type.RESOURCE_NOT_FOUND, resourceName, "Unable to load extension file: " + extension); } addLibraryImports(referencedResource, true); } } } private void migrateExtension(Extension extension, MigrationExecutionContext ctx) throws MigrationException { if (extension instanceof JavaExtensionStatement) { migrateJavaExtension((JavaExtensionStatement) extension, ctx); return; } try { extension.init(ctx); } catch (EvaluationException e) { throw new MigrationException(Type.ANALYZATION_PROBLEMS, resourceName, extension, e); } write("helper "); // assert extension.getParameterTypes().size() > 0; List<String> parameterNames = extension.getParameterNames(); List<EClassifier> parameterTypes = extension.getParameterTypes(); assert parameterNames.size() == parameterTypes.size(); Iterator<String> parameterNamesIterator = parameterNames.iterator(); Iterator<EClassifier> parameterTypesIterator = parameterTypes.iterator(); String selfParameterName = null; if (!OperationCallTrace.isStaticQvtoCall(ctx, extension)) { assert parameterNamesIterator.hasNext(); selfParameterName = parameterNamesIterator.next(); EClassifier selfParameterType = parameterTypesIterator.next(); write(typeManager.getQvtFQName(selfParameterType)); write(OclCs.PATH_SEPARATOR); modelManager.registerSelfAlias(selfParameterName); } write(extension.getName()); write("("); while (parameterNamesIterator.hasNext()) { write(oclKeywordManager.getValidIdentifierValue(parameterNamesIterator.next())); write(" : "); write(typeManager.getQvtFQName(parameterTypesIterator.next())); if (parameterNamesIterator.hasNext()) { write(", "); } } write(") : "); write(typeManager.getQvtFQName(getReturnType(extension, ctx))); writeln(" {"); if (extension instanceof ExpressionExtensionStatement) { migrateExpressionExtension((ExpressionExtensionStatement) extension, ctx); } else if (extension instanceof CreateExtensionStatement) { migrateCreateExtension((CreateExtensionStatement) extension); } else if (extension instanceof WorkflowSlotExtensionStatement) { migrateWorkflowSlotExtension((WorkflowSlotExtensionStatement) extension, ctx); } else { throw new MigrationException(Type.UNSUPPORTED_EXTENSION, resourceName, extension, extension.getClass().getName()); } if (selfParameterName != null) { modelManager.unregisterSelfAlias(selfParameterName); } writeln("}"); } private EClassifier getReturnType(Extension extension, MigrationExecutionContext ctx) throws MigrationException { Set<AnalysationIssue> issues = new HashSet<AnalysationIssue>(); EClassifier returnType = extension.getReturnType(extension.getParameterTypes().toArray(new EClassifier[extension.getParameterNames().size()]), ctx, issues); if (issues.size() > 0) { throw new MigrationException(issues, resourceName, extension); } if (returnType == null) { throw new MigrationException(Type.TYPE_NOT_FOUND, resourceName, extension.getReturnTypeIdentifier(), extension.getReturnTypeIdentifier().getValue()); } return returnType; } private void migrateExpressionExtension(ExpressionExtensionStatement extension, MigrationExecutionContext ctx) throws MigrationException { Map<String, EClassifier> envVariables = new HashMap<String, EClassifier>(); List<String> parameterNames = extension.getParameterNames(); List<EClassifier> parameterTypes = extension.getParameterTypes(); assert parameterNames.size() == parameterTypes.size(); Iterator<String> parameterNamesIterator = parameterNames.iterator(); Iterator<EClassifier> parameterTypesIterator = parameterTypes.iterator(); while (parameterNamesIterator.hasNext()) { envVariables.put(parameterNamesIterator.next(), parameterTypesIterator.next()); } write("\t"); ExpressionAnalyzeTrace expressionAnalyzeTrace = ctx.getTraces().get(extension); // TODO: resolve return type of ExpressionExtensionStatement using // corresponding identifier here in this context and use it as a desired // return type parameter ExpressionMigrationFacade expressionMigrationFacade = new ExpressionMigrationFacade(extension.getExpression(), expressionAnalyzeTrace.getResultType(), envVariables, typeManager, modelManager, new VariableNameDispatcher(extension), ctx, resourceName); StringBuilder expressionContent = expressionMigrationFacade.migrate(); writeln(expressionContent.insert(expressionMigrationFacade.getReturnPosition(), "return ")); } private void migrateJavaExtension(JavaExtensionStatement extension, MigrationExecutionContext ctx) throws MigrationException { javaExtensionDescriptors.add(new JavaExtensionDescriptor(extension, ctx)); if (nativeLibraryClassName == null) { nativeLibraryClassName = JavaExtensionDescriptor.getNativeLibraryName(extension).replaceAll(SyntaxConstants.NS_DELIM, JavaCs.DOT); if (nativeLibraryClassName.lastIndexOf(JavaCs.DOT) > 0) { nativeLibraryPackageName = nativeLibraryClassName.substring(0, nativeLibraryClassName.lastIndexOf(JavaCs.DOT)); nativeLibraryClassName = nativeLibraryClassName.substring(nativeLibraryClassName.lastIndexOf(JavaCs.DOT) + 1); } if (nativeLibraryClassName.length() == 0) { throw new MigrationException(Type.UNABLE_TO_DETECT_NATIVE_LIBRARY_CLASS_NAME, resourceName, extension, "Resource name: \"" + resourceName + "\""); } } } private void migrateCreateExtension(CreateExtensionStatement extension) throws MigrationException { throw new MigrationException(Type.UNSUPPORTED_EXTENSION, resourceName, extension, extension.getClass().getName()); } private void migrateWorkflowSlotExtension(WorkflowSlotExtensionStatement extension, MigrationExecutionContext ctx) throws MigrationException { EClassifier returnType = ctx.getTraces().get(extension).getResultType(); write("\treturn "); // TODO: add more primitive types here (boolean, int) if (returnType == EcorePackage.eINSTANCE.getEString()) { write(stdLibImportsManager.getXpandGetStringGlobalVarOperationName()); } else { write(stdLibImportsManager.getXpandGetObjectGlobalVarOperationName()); if (returnType != EcorePackage.eINSTANCE.getEJavaObject()) { } } write("('"); write(extension.getSlotName().getValue()); write("')"); if (returnType != EcorePackage.eINSTANCE.getEString() && returnType != EcorePackage.eINSTANCE.getEJavaObject()) { write(".oclAsType("); write(typeManager.getQvtFQName(returnType)); write(")"); } writeln(""); } private void write(CharSequence cs, int index) { output.insert(index, cs); } private void write(CharSequence cs) { output.append(cs); } private void writeln(CharSequence line) { output.append(line); output.append(ExpressionMigrationFacade.LF); } }