/* * Copyright (c) 2005, 2008 Borland Software 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: * Artem Tikhomirov (Borland) - initial API and implementation */ package org.eclipse.gmf.tests.setup; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.codegen.ecore.genmodel.GenClass; import org.eclipse.emf.codegen.ecore.genmodel.GenClassifier; import org.eclipse.emf.codegen.ecore.genmodel.GenModel; import org.eclipse.emf.codegen.ecore.genmodel.GenPackage; import org.eclipse.emf.codegen.ecore.genmodel.generator.GenBaseGeneratorAdapter; import org.eclipse.emf.codegen.util.CodeGenUtil; import org.eclipse.emf.common.util.BasicMonitor; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.gmf.codegen.gmfgen.GenDiagram; import org.eclipse.gmf.codegen.gmfgen.GenEditorGenerator; import org.eclipse.gmf.internal.common.codegen.GeneratorBase; import org.eclipse.gmf.tests.CompileUtil; import org.eclipse.gmf.tests.Plugin; import org.eclipse.gmf.tests.gen.GenDiagramMutator; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.junit.Assert; import org.osgi.framework.Constants; /** * Generates and (by default) compiles gmf plugins. * @author artem */ public class GenProjectBaseSetup { protected final Set<String> projectsToInit = new LinkedHashSet<String>(); private CompileUtil compileUtil; private GeneratorConfiguration myGeneratorFactory; private static final String INTERFACE_TEMPLATE = "{0} public interface {1} {2} '{ }'"; public GenProjectBaseSetup(GeneratorConfiguration generatorFactory) { myGeneratorFactory = generatorFactory; } @Deprecated public void generateAndCompile(DiaGenSource diaGenSource) throws Exception { generateAndCompile(diaGenSource.getGenDiagram().getEditorGen()); } public void generateAndCompile(GenEditorGenerator genEditor, GenDiagramMutator... mutators) throws Exception { projectsToInit.clear(); //just in case compileUtil = new CompileUtil(); final GenDiagram d = genEditor.getDiagram(); generateDiagramPrerequisites(d); if (mutators == null || mutators.length == 0) { generateDiagramPlugin(d); } else { for (GenDiagramMutator next : mutators) { next.doMutate(genEditor); try { generateDiagramPlugin(d); } finally { next.undoMutate(genEditor); } } } for (String pluginID : projectsToInit) { IProject p = ResourcesPlugin.getWorkspace().getRoot().getProject(pluginID); hookProjectBuild(p); } compileUtil = null; } protected void generateDiagramPrerequisites(GenDiagram d) throws Exception { final GenModel domainGenModel = d.getEditorGen().getDomainGenModel(); if (domainGenModel != null) { generateEMFCode(domainGenModel); projectsToInit.add(domainGenModel.getModelPluginID()); projectsToInit.add(domainGenModel.getEditPluginID()); } } protected void generateDiagramPlugin(GenDiagram d) throws Exception { GeneratorBase generator = myGeneratorFactory.createGenerator(d); generator.run(); hookGeneratorStatus(generator.getRunStatus()); final String gmfEditorId = d.getEditorGen().getPlugin().getID(); final IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(gmfEditorId); hookExtraCodeGeneration(d.getEditorGen(), project); RuntimeWorkspaceSetup.get().getReadyToStartAsBundle(project); projectsToInit.add(gmfEditorId); hookJDTStatus(project); } /** * no-op by default, subclasses may provide custom code generation in this method * XXX think about more oo way to inject extra codegen artifacts, or even special GMFGen model entities? */ protected void hookExtraCodeGeneration(GenEditorGenerator genEditor, IProject project) throws Exception { } private void generateEMFCode(GenModel domainGenModel) { domainGenModel.setCanGenerate(true); org.eclipse.emf.codegen.ecore.generator.Generator gen = new org.eclipse.emf.codegen.ecore.generator.Generator(); gen.setInput(domainGenModel); gen.generate(domainGenModel, GenBaseGeneratorAdapter.MODEL_PROJECT_TYPE, new BasicMonitor()); gen.generate(domainGenModel, GenBaseGeneratorAdapter.EDIT_PROJECT_TYPE, new BasicMonitor()); fixInstanceClasses(domainGenModel); RuntimeWorkspaceSetup.get().getReadyToStartAsBundle(ResourcesPlugin.getWorkspace().getRoot().getProject(domainGenModel.getModelPluginID())); if (!domainGenModel.getModelPluginID().equals(domainGenModel.getEditPluginID())) { RuntimeWorkspaceSetup.get().getReadyToStartAsBundle(ResourcesPlugin.getWorkspace().getRoot().getProject(domainGenModel.getEditPluginID())); } } private void fixInstanceClasses(GenModel domainGenModel) { @SuppressWarnings("serial") class MultiMap<K, V> extends HashMap<K, LinkedList<V>> { public void putOne(K key, V value) { LinkedList<V> allForKey = get(key); if (allForKey == null) { allForKey = new LinkedList<V>(); put(key, allForKey); } allForKey.add(value); } } final MultiMap<String, GenClassifier> allInstanceClassNames = new MultiMap<String, GenClassifier>(); for (GenPackage nextPackage : domainGenModel.getGenPackages()) { for (GenClassifier nextGenClassifier : nextPackage.getGenClassifiers()) { if (nextGenClassifier.getEcoreClassifier().eIsSet(EcorePackage.Literals.ECLASSIFIER__INSTANCE_CLASS_NAME)) { EClassifier ecoreClassifier = nextGenClassifier.getEcoreClassifier(); allInstanceClassNames.putOne(ecoreClassifier.getInstanceClassName(), nextGenClassifier); } } } if (allInstanceClassNames.isEmpty()) { return; } IPackageFragmentRoot theRoot = null; IFile manifestFile = null; try { String pluginID = domainGenModel.getModelPluginID(); IProject pluginProject = ResourcesPlugin.getWorkspace().getRoot().getProject(pluginID); IJavaProject javaProject = JavaCore.create(pluginProject); Assert.assertNotNull("Generated EMF model project is not a java project", javaProject); IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); for (int i = 0; i < roots.length && theRoot == null; i++) { if (!roots[i].isReadOnly()) { theRoot = roots[i]; } } manifestFile = pluginProject.getFile(JarFile.MANIFEST_NAME); Assert.assertNotNull("Writable project root not found in the generated project", theRoot); Assert.assertTrue("Manifest was not generated", manifestFile != null && manifestFile.exists()); Manifest manifest = new Manifest(manifestFile.getContents()); Attributes attributes = manifest.getMainAttributes(); StringBuffer exportedPackages = new StringBuffer(attributes.getValue(Constants.EXPORT_PACKAGE)); for (String instanceClassName : allInstanceClassNames.keySet()) { List<GenClassifier> eClassifiers = allInstanceClassNames.get(instanceClassName); Assert.assertFalse(eClassifiers.isEmpty()); generateUserInterface(instanceClassName, eClassifiers, theRoot, exportedPackages); } attributes.putValue(Constants.EXPORT_PACKAGE, exportedPackages.toString()); ByteArrayOutputStream contents = new ByteArrayOutputStream(); manifest.write(contents); manifestFile.setContents(new ByteArrayInputStream(contents.toByteArray()), true, true, new NullProgressMonitor()); } catch (Exception e) { e.printStackTrace(); Assert.fail(e.getMessage()); } } private void generateUserInterface(String fqClassName, List<GenClassifier> realizations, IPackageFragmentRoot projectRoot, StringBuffer exportedPackages) { String extendsList = buildExtendsListForUserInterface(realizations); String className = CodeGenUtil.getSimpleClassName(fqClassName); String packageName = CodeGenUtil.getPackageName(fqClassName); if (packageName == null) { packageName = ""; } String packagePrefix = packageName; try { IPackageFragment pkgFragment = projectRoot.createPackageFragment(packageName, true, new NullProgressMonitor()); if (packagePrefix.length() > 0) { packagePrefix = "package " + packagePrefix + ";"; } pkgFragment.createCompilationUnit(className + ".java", MessageFormat.format(INTERFACE_TEMPLATE, new Object[] { packagePrefix, className, extendsList.toString() }), true, new NullProgressMonitor()); } catch (JavaModelException e) { Assert.fail(e.getMessage()); } if (packageName.length() > 0 && exportedPackages.indexOf(packageName) == -1) { exportedPackages.append(","); exportedPackages.append(packageName); } } /** * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=416943, instance interfaces must (since Kepler) extend interfaces for respected EMF superclasses */ private String buildExtendsListForUserInterface(List<GenClassifier> realisations) { if (realisations.size() == 1 && realisations.get(0) instanceof GenClass) { GenClass theOnly = (GenClass) realisations.get(0); return theOnly.getInterfaceExtends(); } SortedSet<GenClass> supers = new TreeSet<GenClass>(new Comparator<GenClass>() { @Override public int compare(GenClass o1, GenClass o2) { return o1.getName().compareTo(o2.getName()); } }); for (GenClassifier next : realisations) { if (next instanceof GenClass) { supers.addAll(((GenClass) next).getExtendedGenClasses()); } } StringBuffer result = new StringBuffer(); result.append("org.eclipse.emf.ecore.EObject"); for (GenClass next : supers) { if (result.length() > 0) { result.append(", "); } result.append(next.getQualifiedInterfaceName()); } return "extends " + result.toString(); } public List<String> getGeneratedProjectNames() { return Collections.unmodifiableList(new LinkedList<String>(projectsToInit)); } protected void hookProjectBuild(IProject p) throws Exception { IStatus s = compileUtil.build(p); if (!s.isOK()) { Plugin.getInstance().getLog().log(s); if (s.getException() != null) { s.getException().printStackTrace(System.err); } else { System.err.println("hookProjectBuild failed without exception:" + s); LinkedList<IStatus> ch = new LinkedList<IStatus>(Arrays.asList(s.getChildren())); while (!ch.isEmpty()) { IStatus f = ch.removeFirst(); if (f.getException() != null) { System.err.println("============> Nested exception in the status:"); f.getException().printStackTrace(System.err); } ch.addAll(Arrays.asList(f.getChildren())); } } Assert.fail(s.getMessage()); } } // FIXME turn verification back once figure templates are ok protected void hookJDTStatus(IProject p) throws Exception { // JDTUtil jdtUtil = new JDTUtil(p); // IStatus jdtStatus = jdtUtil.collectProblems(); // if (!jdtStatus.isOK()) { // Plugin.logError(jdtStatus.getMessage()); // Assert.fail(jdtStatus.getMessage()); // } } protected void hookGeneratorStatus(IStatus generatorStatus) { if (!generatorStatus.isOK()) { Plugin.getInstance().getLog().log(generatorStatus); } if (generatorStatus.getSeverity() == IStatus.ERROR) { Assert.fail("GMF editor generation produced errors:" + generatorStatus.toString()); //$NON-NLS-1$ } } }