/* * Copyright 2015 Nokia Solutions and Networks * Licensed under the Apache License, Version 2.0, * see license.txt file for details. */ package org.robotframework.ide.eclipse.main.plugin.project.editor; import static com.google.common.collect.Lists.newArrayList; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Optional; import javax.inject.Inject; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IStorage; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.e4.core.contexts.ContextInjectionFactory; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.ui.IDecoratorManager; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.part.MultiPageEditorPart; import org.rf.ide.core.executor.RobotRuntimeEnvironment; import org.rf.ide.core.project.RobotProjectConfigReader; import org.robotframework.ide.eclipse.main.plugin.RedPlugin; import org.robotframework.ide.eclipse.main.plugin.model.RobotProject; import org.robotframework.ide.eclipse.main.plugin.navigator.RobotValidationExcludedDecorator; import org.robotframework.ide.eclipse.main.plugin.project.RedEclipseProjectConfigReader; import org.robotframework.ide.eclipse.main.plugin.project.RedEclipseProjectConfigWriter; import org.robotframework.ide.eclipse.main.plugin.project.RobotProjectConfigEvents; import org.robotframework.ide.eclipse.main.plugin.project.editor.RedXmlFileChangeListener.OnRedConfigFileChange; import org.robotframework.ide.eclipse.main.plugin.project.editor.general.GeneralProjectConfigurationEditorPart; import org.robotframework.ide.eclipse.main.plugin.project.editor.libraries.ReferencedLibrariesProjectConfigurationEditorPart; import org.robotframework.ide.eclipse.main.plugin.project.editor.validation.ProjectValidationConfigurationEditorPart; import org.robotframework.ide.eclipse.main.plugin.project.editor.variables.VariablesProjectConfigurationEditorPart; import org.robotframework.red.swt.SwtThread; public class RedProjectEditor extends MultiPageEditorPart { public static final String ID = "org.robotframework.ide.project.editor"; @Inject private IEventBroker eventBroker; private final List<IEditorPart> parts = new ArrayList<>(); private RedProjectEditorInput editorInput; private IResourceChangeListener resourceListener; public RedProjectEditorInput getRedProjectEditorInput() { return editorInput; } @Override protected void setInput(final IEditorInput input) { if (input instanceof FileEditorInput) { final IFile file = ((FileEditorInput) input).getFile(); setPartName(file.getProject().getName() + "/" + input.getName()); editorInput = new RedProjectEditorInput(Optional.of(file), !file.isReadOnly(), new RedEclipseProjectConfigReader().readConfigurationWithLines(file)); installResourceListener(); } else { final IStorage storage = input.getAdapter(IStorage.class); if (storage != null) { setPartName(storage.getName() + " [" + storage.getFullPath() + "]"); try (InputStream stream = storage.getContents()) { editorInput = new RedProjectEditorInput(Optional.<IFile> empty(), !storage.isReadOnly(), new RobotProjectConfigReader().readConfigurationWithLines(stream)); } catch (final CoreException | IOException e) { throw new IllegalProjectConfigurationEditorInputException( "Unable to open editor: unrecognized input of class: " + input.getClass().getName(), e); } } else { throw new IllegalProjectConfigurationEditorInputException( "Unable to open editor: unrecognized input of class: " + input.getClass().getName()); } } super.setInput(input); } @Override protected void createPages() { try { final IEclipseContext context = prepareContext(); addEditorPart(new GeneralProjectConfigurationEditorPart(), "General", context); addEditorPart(new ReferencedLibrariesProjectConfigurationEditorPart(), "Referenced libraries", context); addEditorPart(new VariablesProjectConfigurationEditorPart(), "Variable files", context); addEditorPart(new ProjectValidationConfigurationEditorPart(), "Validation", context); setActivePart(getPageToActivate()); setupEnvironmentLoadingJob(); } catch (final PartInitException e) { throw new EditorInitializationException("Unable to initialize editor", e); } } private IEclipseContext prepareContext() { final IEclipseContext parentContext = getSite().getService(IEclipseContext.class); final IEclipseContext context = parentContext.getActiveLeaf(); context.set(RedProjectEditorInput.class, editorInput); context.set(IEditorSite.class, getEditorSite()); context.set(RedProjectEditor.class, this); ContextInjectionFactory.inject(this, context); return context; } private void installResourceListener() { final IProject project = editorInput.getRobotProject().getProject(); resourceListener = new RedXmlFileChangeListener(project, new OnRedConfigFileChange() { @Override public void whenFileWasRemoved() { SwtThread.syncExec(new Runnable() { @Override public void run() { getSite().getPage().closeEditor(RedProjectEditor.this, false); } }); } @Override public void whenFileChanged() { editorInput.refreshProjectConfiguration(((IFileEditorInput) getEditorInput()).getFile()); setupEnvironmentLoadingJob(); } }); ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceListener, IResourceChangeEvent.POST_CHANGE); } private void addEditorPart(final IEditorPart part, final String title, final IEclipseContext context) throws PartInitException { parts.add(part); ContextInjectionFactory.inject(part, context); setPageText(addPage(part, getEditorInput()), title); } private String getPageToActivate() { if (getEditorInput() instanceof IFileEditorInput) { final IFileEditorInput fileInput = (IFileEditorInput) getEditorInput(); final String projectName = fileInput.getFile().getProject().getName(); final String sectionName = ID + ".activePage." + projectName; final IDialogSettings dialogSettings = RedPlugin.getDefault().getDialogSettings(); final IDialogSettings section = dialogSettings.getSection(sectionName); if (section == null) { return null; } return section.get("activePage"); } return null; } private void setActivePart(final String simpleClassNameOfPage) { for (int i = 0; i < getPageCount(); i++) { final IEditorPart editorPart = getEditor(i); if (editorPart.getClass().getSimpleName().equals(simpleClassNameOfPage)) { setActivePage(i); return; } } setActivePage(0); } private void setupEnvironmentLoadingJob() { final RobotProject project = editorInput.getRobotProject(); final String activeEnv = "activeEnv"; final String allEnvs = "allEnvs"; final Job envLoadingJob = new Job("Reading available frameworks") { @Override protected IStatus run(final IProgressMonitor monitor) { SwtThread.syncExec(() -> eventBroker.send(RobotProjectConfigEvents.ROBOT_CONFIG_ENV_LOADING_STARTED, editorInput.getProjectConfiguration())); final RobotRuntimeEnvironment activeEnvironment = project == null ? null : project.getRuntimeEnvironment(); if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } final List<RobotRuntimeEnvironment> allRuntimeEnvironments = RedPlugin.getDefault() .getAllRuntimeEnvironments(); if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } setProperty(createKey(activeEnv), activeEnvironment); setProperty(createKey(allEnvs), allRuntimeEnvironments); return Status.OK_STATUS; } }; envLoadingJob.addJobChangeListener(new JobChangeAdapter() { @SuppressWarnings("unchecked") @Override public void done(final IJobChangeEvent event) { final RobotRuntimeEnvironment env = (RobotRuntimeEnvironment) envLoadingJob .getProperty(createKey(activeEnv)); final List<RobotRuntimeEnvironment> allEnvironments = (List<RobotRuntimeEnvironment>) envLoadingJob .getProperty(createKey(allEnvs)); SwtThread.syncExec(() -> eventBroker.send(RobotProjectConfigEvents.ROBOT_CONFIG_ENV_LOADED, new Environments(allEnvironments, env))); } }); envLoadingJob.schedule(); } private QualifiedName createKey(final String localName) { return new QualifiedName(RedPlugin.PLUGIN_ID, localName); } @Override public void doSave(final IProgressMonitor monitor) { for (final IEditorPart dirtyEditor : getDirtyEditors()) { dirtyEditor.doSave(monitor); } final RobotProject project = editorInput.getRobotProject(); project.clearAll(); new RedEclipseProjectConfigWriter().writeConfiguration(editorInput.getProjectConfiguration(), project); } private List<? extends IEditorPart> getDirtyEditors() { final List<IEditorPart> dirtyEditors = newArrayList(); for (int i = 0; i < getPageCount(); i++) { final IEditorPart editorPart = getEditor(i); if (editorPart != null && editorPart.isDirty()) { dirtyEditors.add(editorPart); } } return dirtyEditors; } @Override public boolean isSaveAsAllowed() { return false; } @Override public void doSaveAs() { // save as is not allowed } public void openGeneralPage() { setActivePage(getPageIndexFor(GeneralProjectConfigurationEditorPart.class)); } public void openLibrariesPage() { setActivePage(getPageIndexFor(ReferencedLibrariesProjectConfigurationEditorPart.class)); } public void openVariablesFilesPage() { setActivePage(getPageIndexFor(VariablesProjectConfigurationEditorPart.class)); } private int getPageIndexFor(final Class<? extends IEditorPart> classOfPage) { int i = 0; for (final IEditorPart part : parts) { if (classOfPage.isInstance(part)) { return i; } i++; } throw new IllegalStateException("Unable to find part of class: " + classOfPage.getName()); } @Override protected void pageChange(final int newPageIndex) { super.pageChange(newPageIndex); final IEditorPart activeEditor = getActiveEditor(); saveActivePage(activeEditor.getClass().getSimpleName()); } private void saveActivePage(final String activePageClassName) { if (getEditorInput() instanceof IFileEditorInput) { final IFileEditorInput fileInput = (IFileEditorInput) getEditorInput(); final String projectName = fileInput.getFile().getProject().getName(); final String sectionName = ID + ".activePage." + projectName; final IDialogSettings dialogSettings = RedPlugin.getDefault().getDialogSettings(); IDialogSettings section = dialogSettings.getSection(sectionName); if (section == null) { section = dialogSettings.addNewSection(sectionName); } section.put("activePage", activePageClassName); } } @Override public void dispose() { final IEclipseContext parentContext = getEditorSite().getService(IEclipseContext.class); final IEclipseContext context = parentContext.getActiveLeaf(); for (final IEditorPart part : parts) { ContextInjectionFactory.uninject(part, context); } if (resourceListener != null) { ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceListener); } super.dispose(); SwtThread.asyncExec(new Runnable() { @Override public void run() { final IDecoratorManager manager = PlatformUI.getWorkbench().getDecoratorManager(); manager.update(RobotValidationExcludedDecorator.ID); } }); } private static class IllegalProjectConfigurationEditorInputException extends RuntimeException { private static final long serialVersionUID = 1L; public IllegalProjectConfigurationEditorInputException(final String message) { super(message); } public IllegalProjectConfigurationEditorInputException(final String message, final Exception cause) { super(message, cause); } } private static class EditorInitializationException extends RuntimeException { private static final long serialVersionUID = 1L; public EditorInitializationException(final String message, final Throwable cause) { super(message, cause); } } }