/* * Copyright (c) 2006, 2007 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: * Michael Golubev (Borland) - initial API and implementation */ package org.eclipse.gmf.internal.graphdef.codegen.ui; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.MessageFormat; import java.util.Collections; import java.util.Locale; import java.util.ResourceBundle; import java.util.jar.Manifest; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.ContentHandler; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.gmf.gmfgraph.DiagramElement; import org.eclipse.gmf.gmfgraph.FigureGallery; import org.eclipse.gmf.graphdef.codegen.StandaloneGenerator; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.osgi.util.ManifestElement; import org.eclipse.pde.core.plugin.IPluginImport; import org.eclipse.pde.core.plugin.IPluginModel; import org.eclipse.pde.core.plugin.IPluginReference; import org.eclipse.pde.ui.IFieldData; import org.eclipse.pde.ui.templates.BooleanOption; import org.eclipse.pde.ui.templates.OptionTemplateSection; import org.eclipse.pde.ui.templates.TemplateOption; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; public class ConverterSection extends OptionTemplateSection { private static final String MY_PLUGIN_ID = "org.eclipse.gmf.graphdef.codegen.ui"; private static final String SECTION_ID = "org.eclipse.gmf.graphdef.codegen.ui.ConverterSection"; private static final int THE_ONLY_PAGE_INDEX = 0; public static final String OPTION_MAIN_PACKAGE_NAME = SECTION_ID + ".mainPackageName"; public static final String OPTION_NEEDS_MAP_MODE = SECTION_ID + ".needsMapMode"; public static final String OPTION_USE_RUNTIME_FIGURES = SECTION_ID + ".useRuntimeFigures"; public static final String OPTION_INPUT_RESOURCE_FULL_PATH = SECTION_ID + ".inputResource"; public static final String OPTION_OUTPUT_GALLERY_FULL_PATH = SECTION_ID + ".outputGallery"; public static final String OPTION_OUTPUT_DIAGRAM_ELEMENTS_FULL_PATH = SECTION_ID + ".outputDiagramElements"; private TemplateOption myPackageNameOption; private FileNameOption myInputPathOption; private FileNameOption myOutputGalleryPathOption; private FileNameOption myOutputDiagramElementsPathOption; private final InputValidationState myCachedInputValidationState; private BooleanOption myNeedsMapModeOption; private BooleanOption myUseRuntimeFiguresOption; private final boolean shouldWarnLiteVerstionDoesNotSupportMapMode; private ManifestElement[] myRequiredBundles; public ConverterSection(){ setPageCount(THE_ONLY_PAGE_INDEX + 1); myPackageNameOption = addOption(OPTION_MAIN_PACKAGE_NAME, "Generate figures package", null, THE_ONLY_PAGE_INDEX); myInputPathOption = addFileNameOption(false, OPTION_INPUT_RESOURCE_FULL_PATH, "Input GMFGraph instance", "", THE_ONLY_PAGE_INDEX); myOutputGalleryPathOption = addFileNameOption(true, OPTION_OUTPUT_GALLERY_FULL_PATH, "Create Figure Gallery", "", THE_ONLY_PAGE_INDEX); myOutputGalleryPathOption.setRequired(false); myOutputDiagramElementsPathOption = addFileNameOption(true, OPTION_OUTPUT_DIAGRAM_ELEMENTS_FULL_PATH, "Mirror diagram elements", "", THE_ONLY_PAGE_INDEX); myOutputDiagramElementsPathOption.setRequired(false); myNeedsMapModeOption = (BooleanOption) addOption(OPTION_NEEDS_MAP_MODE, "Use IMapMode", false, THE_ONLY_PAGE_INDEX); myUseRuntimeFiguresOption = (BooleanOption) addOption(OPTION_USE_RUNTIME_FIGURES, "Use Enhanced Figures", true, THE_ONLY_PAGE_INDEX); myCachedInputValidationState = new InputValidationState(myOutputGalleryPathOption, myOutputDiagramElementsPathOption); shouldWarnLiteVerstionDoesNotSupportMapMode = Platform.getBundle("org.eclipse.gmf.codegen.lite") != null; } public void addPages(Wizard wizard) { super.addPages(wizard); WizardPage page = createPage(THE_ONLY_PAGE_INDEX); page.setDescription("Converts an existing instance of the gmfgraph model into plugin code"); page.setTitle("Figure definitions converter"); wizard.addPage(page); markPagesAdded(); validateOptions(myPackageNameOption); } public IPluginReference[] getDependencies(String schemaVersion) { // no explicit dependencies return new IPluginReference[0]; } protected void generateFiles(IProgressMonitor monitor) throws CoreException { Resource input = loadResource(myInputPathOption.getText()); StandaloneGenerator.Config config = new StandaloneGeneratorConfigAdapter(this); final ConverterOptions options = newConverterOptions(); final ConverterOutcome converterOutcome = new ConverterOutcome(options, new Resource[] {input}); assert converterOutcome.checkInputAgainstOptions().isOK(); StandaloneGenerator generator = new StandaloneGenerator(converterOutcome.getProcessor(), config); generator.setSkipPluginStructure(false); try { generator.run(new SubProgressMonitor(monitor, 1)); readRequiredBundles(); // XXX readBuildProperties() and use getNewFiles to propagate // XXX readPluginProperties(), use ??? if (!generator.getRunStatus().isOK()){ throw new CoreException(generator.getRunStatus()); } IStatus s = converterOutcome.createResources(new ResourceSetImpl(), URI.createFileURI(myOutputGalleryPathOption.getText()), URI.createFileURI(myOutputDiagramElementsPathOption.getText())); if (s.getSeverity() == IStatus.ERROR) { throw new CoreException(s); } } catch (InterruptedException e) { String message = e.getMessage(); if (message == null){ message = "Interrupted"; } throw new CoreException(new Status(IStatus.ERROR, MY_PLUGIN_ID, 0, message, e)); } catch (IOException ex) { // perhaps, don't need to treat this as error? throw new CoreException(new Status(IStatus.ERROR, MY_PLUGIN_ID, 0, "Failed to read generated manifest.mf", ex)); } finally { input.unload(); } } private ConverterOptions newConverterOptions() { final ConverterOptions options = new ConverterOptions(); options.needMirroredGalleries = shouldGenerate(myOutputGalleryPathOption); options.needMirroredCanvas = shouldGenerate(myOutputDiagramElementsPathOption); options.separateMirrorFiles = options.needMirroredCanvas && myOutputGalleryPathOption.getText().equals(myOutputDiagramElementsPathOption.getText()); return options; } private static boolean shouldGenerate(FileNameOption option){ return option.isEnabled() && !option.isEmpty(); } private void readRequiredBundles() throws CoreException, IOException { try { IFile f = findGeneratedManifest(); if (f == null || !f.exists()) { // fail - we do expect manifest to be there? return; } InputStream is = f.getContents(); String requiredBundles = new Manifest(is).getMainAttributes().getValue(Constants.REQUIRE_BUNDLE); is.close(); myRequiredBundles = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, requiredBundles); } catch (BundleException ex) { throw new IOException(ex.getMessage()); } } private IFile findGeneratedManifest() { return (IFile) project.findMember(new Path("META-INF/MANIFEST.MF")); } public String getPluginActivatorClassFQN(){ return model instanceof IPluginModel ? ((IPluginModel)model).getPlugin().getClassName() : null; } public String getPluginFriendlyName(){ return model.getPluginBase().getName(); } public String getPluginID(){ return model.getPluginBase().getId(); } public String getPluginProviderName() { return model.getPluginBase().getProviderName(); } protected URL getInstallURL() { return getContributingBundle().getEntry("/"); } public String getSectionId() { return SECTION_ID; } public void validateOptions(TemplateOption changed) { if ((myUseRuntimeFiguresOption.equals(changed) || myNeedsMapModeOption.equals(changed)) && shouldWarnLiteVerstionDoesNotSupportMapMode) { boolean useRuntimeFigures = myUseRuntimeFiguresOption.isSelected(); boolean needsMapMode = myNeedsMapModeOption.isSelected(); if (!useRuntimeFigures && needsMapMode) { getTheOnlyPage().setMessage("It is not recommended to use IMapMode for pure-GEF diagram editors", IMessageProvider.INFORMATION); } else { getTheOnlyPage().setMessage(null); } } if (OPTION_NEEDS_MAP_MODE.equals(changed)){ //does not affect state return; } if (validateInputPath() && validatePackageName() && validateOutputOption(myOutputDiagramElementsPathOption) && validateOutputOption(myOutputGalleryPathOption)){ resetPageState(); } } public boolean isDependentOnParentWizard() { return true; } protected void initializeFields(IFieldData data) { super.initializeFields(data); String packageName = getFormattedPackageName(data.getId()); initializeOption(OPTION_MAIN_PACKAGE_NAME, packageName); } protected ResourceBundle getPluginResourceBundle() { return Platform.getResourceBundle(getContributingBundle()); } protected void updateModel(IProgressMonitor monitor) throws CoreException { if (myRequiredBundles == null) { return; } for (int i = 0; i < myRequiredBundles.length; i++) { // take first component, ignore any attributes or directives addImport(myRequiredBundles[i].getValueComponents()[0]); } } private void addImport(String importedPluginId) throws CoreException { IPluginImport pluginImport = model.getPluginFactory().createImport(); pluginImport.setId(importedPluginId); model.getPluginBase().add(pluginImport); } public String[] getNewFiles() { return new String[0]; } public String getUsedExtensionPoint() { return null; } private Bundle getContributingBundle(){ return Platform.getBundle(MY_PLUGIN_ID); } /** * Stolen from PDETemplateSection, which can not be reused due to export limitations. */ private String getFormattedPackageName(String id){ StringBuffer buffer = new StringBuffer(); for (int i = 0; i < id.length(); i++) { char ch = id.charAt(i); if (buffer.length() == 0) { if (Character.isJavaIdentifierStart(ch)) buffer.append(Character.toLowerCase(ch)); } else { if (Character.isJavaIdentifierPart(ch) || ch == '.') buffer.append(ch); } } return buffer.toString().toLowerCase(Locale.ENGLISH); } private FileNameOption addFileNameOption(boolean saveNotLoad, String name, String label, String value, int pageIndex) { FileNameOption result = new FileNameOption(this, name, label, new String[] {"*.gmfgraph"}); result.setSaveNotLoad(saveNotLoad); registerOption(result, value, pageIndex); return result; } private boolean validatePackageName(){ boolean isValid = !myPackageNameOption.isEmpty(); if (!isValid){ flagMissingRequiredOption(myPackageNameOption); } return isValid; } private boolean validateInputPath() { if (myInputPathOption.isEmpty()){ flagMissingRequiredOption(myInputPathOption); myOutputDiagramElementsPathOption.setEnabled(false); myOutputGalleryPathOption.setEnabled(false); return false; } String path = myInputPathOption.getText(); myCachedInputValidationState.updateInput(path); if (!myCachedInputValidationState.isValid()){ flagError(myCachedInputValidationState.getErrorMessage()); return false; } return true; } private boolean validateOutputOption(FileNameOption option) { if (!option.isEnabled()){ return false; } if (!validateMirrorDiagramWithoutFigureGallery()){ return false; } if (option.isEmpty()){ //optional -- ok return true; } String path = option.getText(); return validatePath(path); } private boolean validateMirrorDiagramWithoutFigureGallery(){ if (!myOutputDiagramElementsPathOption.isEmpty()){ if (myOutputGalleryPathOption.isEmpty() || myOutputDiagramElementsPathOption.getText().equals(myOutputGalleryPathOption.getText())){ flagError("In order to mirror diagram elements you have to generate separate figure gallery"); return false; } } return true; } private boolean validatePath(String path){ try { return URI.createFileURI(path) != null; } catch (IllegalArgumentException e){ flagError(MessageFormat.format("Path {0} is invalid", new Object[] {path})); return false; } } private WizardPage getTheOnlyPage() { return getPage(THE_ONLY_PAGE_INDEX); } private void flagError(String message){ getTheOnlyPage().setPageComplete(false); getTheOnlyPage().setErrorMessage(message); } private static Resource loadResource(String path){ Resource resource = new ResourceSetImpl().createResource(URI.createFileURI(path), ContentHandler.UNSPECIFIED_CONTENT_TYPE); try { resource.load(Collections.EMPTY_MAP); return resource; } catch (IOException e) { return null; } } private static class InputValidationState { private String myCachedPath; private String myCachedErrorMessage; private boolean myHasDiagramElement; private boolean myHasFigure; private final FileNameOption myDiagramElementsOption; private final FileNameOption myGalleryOption; public InputValidationState(FileNameOption galleryOption, FileNameOption diagramElementsOption){ myGalleryOption = galleryOption; myDiagramElementsOption = diagramElementsOption; } public void updateInput(String path){ if (myCachedPath == null || !myCachedPath.equals(path)){ myCachedPath = path; validateInputPath(path); myGalleryOption.setEnabled(myHasFigure); myDiagramElementsOption.setEnabled(myHasDiagramElement); } } public boolean isValid(){ return myHasFigure; } public String getErrorMessage(){ return myCachedErrorMessage; } private void validateInputPath(String path) { myHasDiagramElement = false; myHasFigure = false; myCachedErrorMessage = null; if (path == null || !new File(path).exists()){ myCachedErrorMessage = MessageFormat.format("Can not find file {0}", new Object[] {path}); return; } Resource resource = loadResource(path); if (resource != null){ classifyContents(resource); } if (!myHasFigure){ myCachedErrorMessage = MessageFormat.format("File {0} does not contain any figure definitions", new Object[] {path}); } } private void classifyContents(Resource resource){ myHasDiagramElement = false; myHasFigure = false; for (TreeIterator<EObject> contents = resource.getAllContents(); contents.hasNext();){ EObject next = contents.next(); if (next instanceof FigureGallery){ if (!myHasFigure){ FigureGallery nextGallery = (FigureGallery) next; myHasFigure = !nextGallery.getFigures().isEmpty(); } contents.prune(); } if (next instanceof DiagramElement){ myHasDiagramElement = true; contents.prune(); } if (myHasDiagramElement && myHasFigure){ break; } } } } }