/******************************************************************************* * Copyright (c) 2006 Business Objects Software Limited and others. * All rights reserved. * This file is 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: * Business Objects Software Limited - initial API and implementation based on Eclipse 3.1.2 code for * /org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/util/CoreUtility.java, and * org.eclipse.jdt.internal.ui.javaeditor.EditorUtility * Eclipse source is available at: http://www.eclipse.org/downloads/ *******************************************************************************/ /* * CoreUtility.java * Creation date: Feb 15, 2006. * By: Edward Lam */ package org.openquark.cal.eclipse.ui.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceDescription; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.IWorkspace.ProjectOrder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; 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.core.runtime.jobs.Job; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.jdt.internal.core.JarEntryFile; import org.eclipse.jdt.internal.ui.javaeditor.JarEntryEditorInput; import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.window.Window; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorDescriptor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorRegistry; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.dialogs.ListDialog; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.IEditorStatusLine; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.TextEditorAction; import org.openquark.cal.compiler.CALDocComment; import org.openquark.cal.compiler.ClassInstance; import org.openquark.cal.compiler.ClassMethod; import org.openquark.cal.compiler.CompilerMessage; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.DataConstructor; import org.openquark.cal.compiler.Function; import org.openquark.cal.compiler.FunctionalAgent; import org.openquark.cal.compiler.MessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ScopedEntity; import org.openquark.cal.compiler.SearchResult; import org.openquark.cal.compiler.SourceIdentifier; import org.openquark.cal.compiler.SourceMetricsManager; import org.openquark.cal.compiler.SourcePosition; import org.openquark.cal.compiler.SourceRange; import org.openquark.cal.compiler.TypeClass; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.compiler.SearchResult.Precise; import org.openquark.cal.eclipse.core.CALEclipseCorePlugin; import org.openquark.cal.eclipse.core.CALModelManager; import org.openquark.cal.eclipse.core.CALModelMarker; import org.openquark.cal.eclipse.core.builder.CALBuilder; import org.openquark.cal.eclipse.core.util.Util; import org.openquark.cal.eclipse.ui.CALEclipseUIPlugin; import org.openquark.cal.eclipse.ui.CALUIMessages; import org.openquark.cal.eclipse.ui.actions.ActionMessages; import org.openquark.cal.eclipse.ui.caleditor.CALEditor; import org.openquark.cal.eclipse.ui.caleditor.PartiallySynchronizedDocument; import org.openquark.cal.eclipse.ui.search.SearchMessages; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.osgi.framework.Bundle; /** * Helper utility functions for this plugin. * @author Edward Lam */ public final class CoreUtility { /** The lecc_runtime folder path - used by addLeccFolderSrcExclude. */ private static final IPath leccRuntimeFolderPath = new Path("lecc_runtime/"); /** * Private constructor - do not instantiate. */ private CoreUtility() { } /** * Creates an extension. If the extension plugin has not * been loaded a busy cursor will be activated during the duration of * the load. * * @param element the config element defining the extension * @param classAttribute the name of the attribute carrying the class * @return the extension object */ public static Object createExtension(final IConfigurationElement element, final String classAttribute) throws CoreException { // If plugin has been loaded create extension. // Otherwise, show busy cursor then create extension. String pluginId = element.getNamespace(); Bundle bundle = Platform.getBundle(pluginId); if (bundle != null && bundle.getState() == Bundle.ACTIVE) { return element.createExecutableExtension(classAttribute); } else { final Object[] ret = new Object[1]; final CoreException[] exc = new CoreException[1]; BusyIndicator.showWhile(null, new Runnable() { public void run() { try { ret[0] = element.createExecutableExtension(classAttribute); } catch (CoreException e) { exc[0] = e; } } }); if (exc[0] != null) { throw exc[0]; } else { return ret[0]; } } } /** * Starts a build in the background. * @param project The project to build or <code>null</code> to build the workspace. * @param forCalOnly if true, full build will only apply for the cal portion of the project. */ public static void startBuildInBackground(final IProject project, boolean forCalOnly) { getBuildJob(project, forCalOnly).schedule(); } /** * A job to build a project or workspace. * @author Edward Lam */ private static final class BuildJob extends Job { private final IProject fProject; private final boolean clean; private final boolean forCalOnly; /** * @param name the name of the job * @param clean if true, this is a clean job. if false, this is a build job. * @param project The project to build or null if all projects are to be built. * @param forCalOnly */ private BuildJob(String name, IProject project, boolean clean, boolean forCalOnly) { super(name); fProject = project; this.clean = clean; this.forCalOnly = forCalOnly; } public boolean isCoveredBy(BuildJob other) { if (other.fProject == null) { return true; } return fProject != null && fProject.equals(other.fProject); } /* * (non-Javadoc) * * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) */ @Override protected IStatus run(IProgressMonitor monitor) { synchronized (getClass()) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } Job[] buildJobs = Platform.getJobManager().find(ResourcesPlugin.FAMILY_MANUAL_BUILD); for (final Job curr : buildJobs) { if (curr != this && curr instanceof BuildJob) { BuildJob job = (BuildJob)curr; if (job.isCoveredBy(this)) { curr.cancel(); // cancel all other build jobs of our kind } } } } try { if (fProject != null) { // Build a project and its dependents. String taskName = clean ? CALUIMessages.CoreUtility_cleanproject_taskname : CALUIMessages.CoreUtility_buildproject_taskname; monitor.beginTask(Messages.format(taskName, fProject.getName()), 4); if (clean) { fProject.build(IncrementalProjectBuilder.CLEAN_BUILD, CALEclipseCorePlugin.BUILDER_ID, null, new SubProgressMonitor(monitor, 4)); } else { // First build the specific project. if (forCalOnly && fProject.hasNature(JavaCore.NATURE_ID)) { // incremental build for java. // full build for cal. fProject.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, JavaCore.BUILDER_ID, null, new SubProgressMonitor(monitor, 1)); fProject.build(IncrementalProjectBuilder.FULL_BUILD, CALEclipseCorePlugin.BUILDER_ID, null, new SubProgressMonitor(monitor, 1)); } else { // Build everything. fProject.build(IncrementalProjectBuilder.FULL_BUILD, new SubProgressMonitor(monitor, 2)); } // Now invoke the incremental builder to build dependents. CALEclipseUIPlugin.getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new SubProgressMonitor(monitor, 2)); } } else { // Build all. String taskName = clean ? CALUIMessages.CoreUtility_cleanall_taskname : CALUIMessages.CoreUtility_buildall_taskname; if (forCalOnly) { // TODOEL: this does not handle cycles (knots) in the project order. ProjectOrder projectOrder = ResourcesPlugin.getWorkspace().computeProjectOrder(CALEclipseUIPlugin.getWorkspace().getRoot().getProjects()); IProject[] projects = projectOrder.projects; monitor.beginTask(taskName, projects.length*2); for (final IProject project : projects) { if (clean) { // clean build. project.deleteMarkers(CALModelMarker.CAL_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); project.build(IncrementalProjectBuilder.CLEAN_BUILD, CALEclipseCorePlugin.BUILDER_ID, null, new SubProgressMonitor(monitor, 2)); } else { // incremental build for java. if (project.hasNature(JavaCore.NATURE_ID)) { // Don't need to delete the markers, but if a Java file is being edited it must be saved before this compilation is invoked // if the user wants the Java build to remove existing Java build problem markers. project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, JavaCore.BUILDER_ID, null, new SubProgressMonitor(monitor, 1)); } // full build for cal. project.deleteMarkers(CALModelMarker.CAL_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); project.build(IncrementalProjectBuilder.FULL_BUILD, CALEclipseCorePlugin.BUILDER_ID, null, new SubProgressMonitor(monitor, 1)); } } } else { monitor.beginTask(taskName, 2); if (clean) { CALEclipseUIPlugin.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, new SubProgressMonitor(monitor, 2)); } else { CALEclipseUIPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, new SubProgressMonitor(monitor, 2)); } } } } catch (CoreException e) { return e.getStatus(); } catch (OperationCanceledException e) { return Status.CANCEL_STATUS; } finally { monitor.done(); } initializationIsComplete = true; return Status.OK_STATUS; } @Override public boolean belongsTo(Object family) { return ResourcesPlugin.FAMILY_MANUAL_BUILD == family; } } /** * This is a temporary workaround. The Program object is not initialized correctly until * the compiler is run the first time. This cannot be done during startup because the * startup must be fast. This flag is used by the search to determine if the compiler * will be run before the search is started. This needs to be done only once. This * is not thread safe but I don't think that multiple version of this can run at the same * time because of the way the Eclipse UI works. After this check-in. I will fix the problem * the correct way and remove this hack. * * TODO Remove this hack after the CALBuilder is initializing correctly. */ private static boolean initializationIsComplete = false; /** * @return true if the type information was loaded by the CALBuilder. The CALBuilder can be enabled * but not have the type information loaded because of a configuration bug. This will be fixed in the * future * TODO GJM: remove this hack */ public static boolean calBuilderWasInitialized(){ return initializationIsComplete; } /** * Whether or not the initializeCALBuilderJob job was run. */ private static boolean initializeJobWasStarted = false; private static Job initializeCALBuilderJob = CoreUtility.getBuildJob(null, true); /** * Initializes the CALBuilder. This is a temporary hack until the CALBuilder is initialized properly. * @param monitor * @return true iff the CALBuilder needed to be initialized. */ public static boolean initializeCALBuilder(IProgressMonitor monitor, int totalTicks, int ticks){ if (initializationIsComplete){ return false; } else{ // If the monitor is null then pop up a dialog. if (monitor == null){ final ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(null); try { progressMonitorDialog.run(false, false, new IRunnableWithProgress() { public void run(IProgressMonitor monitor) { CoreUtility.initializeCALBuilder(progressMonitorDialog.getProgressMonitor(), 100, 100); monitor.done(); }}); return true; } catch (InvocationTargetException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return false; } catch (InterruptedException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return false; } } else{ // This is called from the UI thread so cannot be called more than once at the same // time so I am not syncing this. if (initializeJobWasStarted){ // job is or might be still running so just wait for it to complete. try { initializeCALBuilderJob.join(); } catch (InterruptedException e) { } } else{ // The builder has not been initialize so just run it now. monitor.beginTask(SearchMessages.SearchPage_initializingTypeInfo, totalTicks); // Ensure that the CAL module information is loaded initializeCALBuilderJob.setUser(true); CoreUtility.runJob(initializeCALBuilderJob, new SubProgressMonitor(monitor, ticks)); } return true; } } } /** * Initializes the CALBuilder. This will load the CAL information in the background */ public static void initializeCALBuilderInBackground(){ // This sync is to make sure that the build job is run only once. synchronized(initializeCALBuilderJob){ if (!initializeJobWasStarted){ initializeCALBuilderJob.setUser(false); initializeCALBuilderJob.schedule(); initializeJobWasStarted = true; } } } /** * Helper function for running the build job in the current thread. * @param job The job to run. * @param monitor The progress monitor for the job. */ private static void runJob(Job job, IProgressMonitor monitor){ BuildJob buildJob = (BuildJob) job; buildJob.run(monitor); } /** * Returns a build job * * @param project The project to build or <code>null</code> to build the workspace. * @param forCalOnly if true, full build will only apply for the cal portion of the project. * If false, full build will apply for the whole project. */ public static Job getBuildJob(final IProject project, boolean forCalOnly) { Job buildJob = new BuildJob(CALUIMessages.CoreUtility_job_title, project, false, forCalOnly); buildJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule()); buildJob.setUser(true); return buildJob; } /** * Returns a clean job * * @param project The project to clean or <code>null</code> to clean the workspace. * @param forCalOnly if true, clean will only apply for the cal portion of the project. * If false, clean will apply for the whole project. */ public static Job getCleanJob(final IProject project, boolean forCalOnly) { Job buildJob = new BuildJob(CALUIMessages.CoreUtility_clean_job_title, project, true, forCalOnly); buildJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule()); buildJob.setUser(true); return buildJob; } /** * Set the autobuild to the value of the parameter and * return the old one. * * @param state the value to be set for autobuilding. * @return the old value of the autobuild state */ public static boolean enableAutoBuild(boolean state) throws CoreException { IWorkspace workspace = ResourcesPlugin.getWorkspace(); IWorkspaceDescription desc = workspace.getDescription(); boolean isAutoBuilding = desc.isAutoBuilding(); if (isAutoBuilding != state) { desc.setAutoBuilding(state); workspace.setDescription(desc); } return isAutoBuilding; } /** * Convert the given source position to an offset into the document. * @param sp The source position to convert * @param document The document to get the offset to the source position * @return The offset in the document that corresponds to the given source position. This is a zero based index. * @throws BadLocationException */ public static int toOffset(SourcePosition sp, IDocument document) throws BadLocationException{ return CoreUtility.convertToCharacterPosition(sp.getLine(), sp.getColumn(), document); } /** * Convert the given source range to a length in the document. * @param sourceRange The source range to convert * @param document The document to get the offset to the source range * @return the length of the source range in the given document. * @throws BadLocationException */ public static int toLength(SourceRange sourceRange, IDocument document) throws BadLocationException{ final int start = toOffset(sourceRange.getStartSourcePosition(), document); final int end = toOffset(sourceRange.getEndSourcePosition(), document); return end - start; } /** * Convert the given source range to an IRegion in the document. Source ranges have positions * in terms of editor columns. The IRegion has it in terms of offsets in the document. This is * an issue when the documents contain tabs. * @param sourceRangeInOrg The source range to convert * @param psd The document to get the offset to the source range * @return the IRegion of the source range in the given document. * @throws BadLocationException */ public static IRegion toRegion(SourceRange sourceRangeInOrg, PartiallySynchronizedDocument psd) throws BadLocationException{ // Org suffix means that the co-ordinates are in the original document that the source position is based on // Cur suffix means that the co-ordinates are in the current document's space final int startInOrg = toOffset(sourceRangeInOrg.getStartSourcePosition(), psd.getOriginalDocument()); final int startInCur = psd.fromOriginalOffset(startInOrg); int lineOffsetInOrg = psd.getOriginalDocument().getLineOffset(sourceRangeInOrg.getEndLine() - 1); final int tabSize = getTabSize(); int columnInEditor = 1; // source range goes one column past the end. The last position may be on // a non-existant space character on the end of the line final int columnToFind = sourceRangeInOrg.getEndColumn() - 1; int i = 0; final int currentLength = psd.getLength(); while (columnInEditor < columnToFind){ final int pos = i + lineOffsetInOrg; final char ch = (pos >= currentLength) ? ' ' : psd.getChar(pos); if (ch != '\t') { columnInEditor++; } else { //tabs can consume from 1 to tabSize columns (a tab character moves the column to the next tab stop) final int jump = (((columnInEditor-1)/tabSize) + 1) * tabSize + 1 - columnInEditor; columnInEditor += jump; } ++i; } final int endInOrg = i + lineOffsetInOrg; final int endInCur = psd.fromOriginalOffset(endInOrg); final int length = endInCur - startInCur + 1; return new IRegion(){ public int getLength() { return length; } public int getOffset() { return startInCur; } }; } public static void showPosition(IEditorPart editorPart, IStorage sourceStorage, SourceRange range){ showPosition(editorPart, sourceStorage, null, range, false); } public static void showPosition(IEditorPart editorPart, IStorage sourceStorage, SourceRange range, boolean startOnly){ showPosition(editorPart, sourceStorage, null, range, startOnly); } public static void showPosition(IEditorPart editorPart, IStorage sourceStorage, PartiallySynchronizedDocument psd, SourceRange range, boolean startOnly){ if (editorPart instanceof ITextEditor) { ITextEditor textEditor = (ITextEditor) editorPart; if (sourceStorage instanceof IFile) { IFile sourceFile = (IFile) sourceStorage; if (sourceFile == null || !sourceFile.exists()){ return; } } IDocument doc = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()); try { int start; int end; if (psd != null){ start = toOffset(range.getStartSourcePosition(), psd.getOriginalDocument()); start = psd.fromOriginalOffset(start); end = toOffset(range.getEndSourcePosition(), psd.getOriginalDocument()); end = psd.fromOriginalOffset(end); } else{ start = toOffset(range.getStartSourcePosition(), doc); end = toOffset(range.getEndSourcePosition(), doc); } final int length = startOnly ? 0 : end - start; textEditor.selectAndReveal(start, length); } catch (BadLocationException e) { // will only happen on concurrent modification CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ } } } /** * Opens an editor on the given file resource. * * @param storage The storage to open an editor for. * @param activate if <code>true</code> the editor will be activated * @return an open editor or <code>null</code> if an external editor was opened * @throws PartInitException */ @SuppressWarnings("restriction") public static IEditorPart openInEditor(IStorage storage, boolean activate) throws PartInitException { IEditorPart editorPart = null; if (storage != null) { IWorkbenchPage ap = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); if (ap != null) { if (storage instanceof IFile) { IFile file = (IFile) storage; editorPart = IDE.openEditor(ap, file, activate); initializeHighlightRange(editorPart); } else if (storage instanceof JarEntryFile) { JarEntryFile jarEntry = (JarEntryFile) storage; editorPart = IDE.openEditor(ap, new JarEntryEditorInput(jarEntry), CALEclipseUIPlugin.EDITOR_ID); } } } return editorPart; } private static IEditorInput getEditorInput(IJavaElement element){ while (element != null) { if (element instanceof ICompilationUnit) { ICompilationUnit unit = (ICompilationUnit) element; IResource resource = unit.getResource(); if (resource instanceof IFile) { return new FileEditorInput((IFile) resource); } } element= element.getParent(); } return null; } public static IEditorPart openJavaElementInEditor(IJavaElement inputElement, ISourceRange sourceRange) throws PartInitException{ final boolean activate = true; IEditorInput input= getEditorInput(inputElement); if (input != null){ final IEditorPart editorPart = openInEditor(input, getEditorID(input, inputElement), activate); if (editorPart instanceof ITextEditor) { ITextEditor textEditor = (ITextEditor) editorPart; textEditor.selectAndReveal(sourceRange.getOffset(), sourceRange.getLength()); } return editorPart; } return null; } private static IEditorPart openInEditor(IEditorInput input, String editorID, boolean activate) throws PartInitException { if (input != null) { IWorkbenchPage ap = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); if (ap != null) { IEditorPart editorPart= ap.openEditor(input, editorID, activate); initializeHighlightRange(editorPart); return editorPart; } } return null; } public static String getEditorID(IEditorInput input, Object inputObject) { IEditorRegistry registry= PlatformUI.getWorkbench().getEditorRegistry(); IEditorDescriptor descriptor= registry.getDefaultEditor(input.getName()); if (descriptor != null) { return descriptor.getId(); } return null; } public static void initializeHighlightRange(IEditorPart editorPart) { if (editorPart instanceof ITextEditor) { IAction toggleAction = editorPart .getEditorSite() .getActionBars() .getGlobalActionHandler( ITextEditorActionDefinitionIds.TOGGLE_SHOW_SELECTED_ELEMENT_ONLY); boolean enable = toggleAction != null; enable = enable && toggleAction.isEnabled() && toggleAction.isChecked(); if (enable) { if (toggleAction instanceof TextEditorAction) { // Reset the action ((TextEditorAction) toggleAction).setEditor(null); // Restore the action ((TextEditorAction) toggleAction) .setEditor((ITextEditor) editorPart); } else { // Un-check toggleAction.run(); // Check toggleAction.run(); } } } } /** * Shows the given error on the status line that appears to the bottom left side of the editor. * @param textEditor the text editor that the status message is shown under. * @param message the error message to show. */ public static void showErrorOnStatusLine(ITextEditor textEditor, String message){ showErrorOnStatusLine(textEditor, message, true); } /** * Shows the given error on the status line that appears to the bottom left side of the editor. * @param textEditor the text editor that the status message is shown under. * @param message the error message to show. */ public static void showErrorOnStatusLine(ITextEditor textEditor, String message, boolean performBeep){ IEditorStatusLine statusLine = (IEditorStatusLine) textEditor.getAdapter(IEditorStatusLine.class); if (statusLine != null){ statusLine.setMessage(true, message, null); } if (performBeep){ textEditor.getSite().getShell().getDisplay().beep(); } } /** * Convert to given line and column position into an offset in the document. * @param line the line number of selected position. Starts at one. * @param columnToFind the column number of the selected position. Starts at one. * @param document the document to find the selected position in. * @return the offset in characters into the current file that corresponds to the given line and column position. This is a zero based index. * @throws BadLocationException */ public static int convertToCharacterPosition(int line, int columnToFind, IDocument document) throws BadLocationException { int lineOffsetInDocument = document.getLineOffset(line - 1); final int tabSize = getTabSize(); // Start the columns from zero instead of one so the jump math works properly int currentColumn = 1; int i = 0; while (currentColumn < columnToFind){ char ch = document.getChar(i + lineOffsetInDocument); if (ch != '\t') { currentColumn++; } else { //tabs can consume from 1 to tabSize columns (a tab character moves the column to the next tab stop) int jump = (((currentColumn-1)/tabSize) + 1) * tabSize + 1 - currentColumn; currentColumn += jump; } ++i; } return i + lineOffsetInDocument; } /** * @param line * @param offsetInDocument * @param document * @return the column corresponding to the given offset. The column starts at zero. * @throws BadLocationException */ public static int getColumn(int line, int offsetInDocument, IDocument document) throws BadLocationException{ int lineOffsetInDocument = document.getLineOffset(line); int characterInDocument = offsetInDocument - lineOffsetInDocument; int tabSize = getTabSize(); int columnInEditor = 0; for (int i = 0; i < characterInDocument; i ++) { char ch = document.getChar(i + lineOffsetInDocument); if (ch != '\t') { columnInEditor++; } else { //tabs can consume from 1 to tabSize columns (a tab character moves the column to the next tab stop) int jump = (((columnInEditor)/tabSize) + 1) * tabSize - columnInEditor; columnInEditor += jump; } } return columnInEditor; } private static int getTabSize() { return getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, 4); } private static int getCoreFormatterOption(String key, int def) { try { return Integer.parseInt(getCoreFormatterOption(key)); } catch (NumberFormatException e) { return def; } } private static String getCoreFormatterOption(String key) { return CALEclipseCorePlugin.getOption(key); } /** * This class is passed into an error dialog to show one error message. * * @author Greg McClement */ private static class DialogStatus implements IStatus{ private final int severity; private final String message; public DialogStatus(int severity, String message){ this.severity = severity; this.message = message; } public String getMessage(){ return message; } public IStatus[] getChildren() { return new IStatus[0]; } public int getCode() { return severity; } public Throwable getException() { return null; } public String getPlugin() { return null; } public int getSeverity() { return severity; } public boolean isMultiStatus() { return false; } public boolean isOK() { return false; } public boolean matches(int severityMask) { return (severityMask & severity) != 0; } } /** * This class is passed into an error dialog to show only the Severity.ERROR messages of * a compiler message logger. * * @author Greg McClement */ private static class CompilerMessagesStatus implements IStatus{ private final String message; private final IStatus[] children; public CompilerMessagesStatus(final String message, final CompilerMessageLogger messageLogger){ this.message = message; // Build a list of error messages to remove duplicates final HashSet<String> errorMessages = new HashSet<String>(); { List<CompilerMessage> messages = messageLogger.getCompilerMessages(CompilerMessage.Severity.ERROR); for (final CompilerMessage compilerMessage : messages) { errorMessages.add(compilerMessage.toString()); } } // build a status object for each error message { children = new IStatus[errorMessages.size()]; final Iterator<String> iter = errorMessages.iterator(); for (int i = 0; i < children.length; ++i) { final String errorMessage = iter.next(); children[i] = new DialogStatus(ERROR, errorMessage); } } } public IStatus[] getChildren() { return children; } public int getCode() { return ERROR; } public Throwable getException() { return null; } public String getPlugin() { return null; } public int getSeverity() { return ERROR; } public boolean isMultiStatus() { return true; } public boolean isOK() { return false; } public boolean matches(int severityMask) { return (severityMask & ERROR) != 0; } public String getMessage() { return message; } } public static void showMessage(final String title, final String message, final int severity){ showMessage(title, new DialogStatus(severity, message)); } public static void showMessage(final String title, Exception exception){ showMessage(title, exception.toString(), IStatus.ERROR); } public static void showMessage(final String title, IStatus status){ ErrorDialog.openError( null, title, null, status ); } /** * This function will return true if the CAL builder is enabled. If the builder is * not enabled then an error message will be shown in the status line. * @param textEditor The text editor to use to update the status line on * @return True if the builder was enabled. */ public static boolean builderEnabledCheck(CALEditor textEditor){ if (!CALBuilder.isEnabled()){ showErrorOnStatusLine(textEditor, ActionMessages.error_calBuilderNotEnabled_message); return false; // Cannot run action since the build is not enabled. } return true; } /** * @param title Title for the error dialog if the build is not enabled. * @return True if the builder was enabled. */ public static boolean builderEnabledCheck(final String title){ /** * This feature only works if the CALBuilder is enabled. I was going to disable * it but I think having it selectable and showing a message that says how to make * it work is more friendly than just being disabled and having the user guess how * to turn it on. */ if (!CALBuilder.isEnabled()){ if (title != null){ showMessage(title, ActionMessages.error_calBuilderNotEnabled_message, IStatus.ERROR); } return false; // Cannot run action since the build is not enabled. } return true; } /** * Show the given error messages to the user in a dialog. * @param title The title to use for the error dialog. * @param message A message describing the general operation that failed. * @param messageLogger The message logger containing the errors to show. * @return True if there was any errors. */ public static boolean showErrors(String title, final String message, final CompilerMessageLogger messageLogger){ if (messageLogger.getNErrors() == 0){ return false; } showMessage(title, new CompilerMessagesStatus(message, messageLogger)); return true; } /** * This class will show a dialog to allow the user to save all the dirty editors in the workspace. * If open returns Dialog.OK then all the dirty editors have been saved. * * @author Greg McClement */ public static class SaveAllDirtyEditors extends ListDialog{ private final IEditorPart[] dirtyEditors = getDirtyEditors(); public SaveAllDirtyEditors(Shell parent) { super(parent); setTitle(CALUIMessages.CoreUtilities_SaveAllModifiedResource_Title); setMessage(CALUIMessages.CoreUtilities_SaveAllModifiedResource_Message); setContentProvider( new IStructuredContentProvider() { public Object[] getElements(Object inputElement) { return dirtyEditors; } public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } ); setLabelProvider( new LabelProvider(){ @Override public String getText(Object element){ IEditorPart editorPart = (IEditorPart) element; return editorPart.getTitle(); } } ); setInput(Arrays.asList(dirtyEditors)); } @Override public int open(){ if (dirtyEditors.length == 0){ // all the dirty editors have been saved. return Window.OK; } else{ if (super.open() == Window.OK){ final ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(null); try { progressMonitorDialog.run(false, false, new IRunnableWithProgress() { public void run(IProgressMonitor monitor) { monitor.beginTask(CALUIMessages.CoreUtilities_SaveAllModifiedResource_SavingFilesProgress, dirtyEditors.length); for(int i = 0; i < dirtyEditors.length; ++i){ dirtyEditors[i].doSave(monitor); monitor.worked(1); } monitor.done(); }}); } catch (InvocationTargetException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return Window.CANCEL; } catch (InterruptedException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return Window.CANCEL; } } return getReturnCode(); } } } /** * @return A list of all the editors that are dirty */ private static IEditorPart[] getDirtyEditors(){ IWorkbench workbench = PlatformUI.getWorkbench(); IWorkbenchWindow[] workbenchWindows = workbench.getWorkbenchWindows(); List<IEditorPart> dirtyEditors = new ArrayList<IEditorPart>(); for(int i = 0; i < workbenchWindows.length; ++i){ IWorkbenchWindow workbenchWindow = workbenchWindows[i]; IWorkbenchPage[] pages = workbenchWindow.getPages(); for(int j = 0; j < pages.length; ++j){ IWorkbenchPage page = pages[j]; IEditorPart[] editors = page.getDirtyEditors(); for(int k = 0; k < editors.length; ++k){ dirtyEditors.add(editors[k]); } } } return dirtyEditors.toArray(new IEditorPart[dirtyEditors.size()]); } /** * Ensures that the CAL nature is on for the given project. * * @param project * to have sample nature added or removed */ public static void turnOnNature(IProject project) { try { IProjectDescription description = project.getDescription(); String[] natures = description.getNatureIds(); for (int i = 0; i < natures.length; ++i) { if (CALEclipseCorePlugin.NATURE_ID.equals(natures[i])) { // already on so leave it on return; } } CoreUtility.addCALNature(project, description, natures); } catch (CoreException e) { // Couldn't toggle the nature. // TODOEL: Handle this. CALEclipseUIPlugin.logException(e); } } /** * Ensure that a source exclude pattern for the lecc_runtime/ folder exists for the corresponding location for the given cal file. * so that the package explorer isn't littered. * @param calFile the cal file for which the corresponding lecc_runtime output folder should be added to the source excludes. */ public static void ensureLeccFolderSrcExclude(IFile calFile) { IProject project = calFile.getProject(); IJavaProject javaProject = JavaCore.create(project); IClasspathEntry[] rawClasspath; try { rawClasspath = javaProject.getRawClasspath(); } catch (JavaModelException e) { Util.log(e, "Exception getting raw classpath for project: " + project); return; } for (int i = 0; i < rawClasspath.length; i++) { IClasspathEntry entry = rawClasspath[i]; switch (entry.getEntryKind()) { case IClasspathEntry.CPE_SOURCE: { if (entry.getPath().isPrefixOf(calFile.getFullPath())) { // match IPath[] exclusionPatterns = entry.getExclusionPatterns(); // See if the exclusion pattern already exists. for (IPath exclusionPath : exclusionPatterns) { if (leccRuntimeFolderPath.equals(exclusionPath)) { // already has the exclusion pattern. return; } } // Does not have the exclusion pattern so add it. IPath[] newExclusionPatterns = new IPath[exclusionPatterns.length + 1]; System.arraycopy(exclusionPatterns, 0, newExclusionPatterns, 0, exclusionPatterns.length); newExclusionPatterns[exclusionPatterns.length] = leccRuntimeFolderPath; // Create a new raw classpath with the entry replaced. IClasspathEntry[] newRawClasspath = rawClasspath.clone(); newRawClasspath[i] = JavaCore.newSourceEntry( entry.getPath(), entry.getInclusionPatterns(), newExclusionPatterns, entry.getOutputLocation(), entry.getExtraAttributes()); try { javaProject.setRawClasspath(newRawClasspath, null); } catch (JavaModelException e) { Util.log(e, "Exception setting raw classpath for project: " + project); return; } return; } break; } default: // not a source entry. break; } } // Couldn't find the source folder for the file. } /** * Adds the CAL nature to the given project. */ public static void addCALNature(IProject project, IProjectDescription description, String[] natures) throws CoreException { // Add the nature String[] newNatures = new String[natures.length + 1]; System.arraycopy(natures, 0, newNatures, 0, natures.length); newNatures[natures.length] = CALEclipseCorePlugin.NATURE_ID; description.setNatureIds(newNatures); project.setDescription(description, null); } /** * Toggles sample nature on a project * * @param project * to have sample nature added or removed */ public static void toggleNature(IProject project) { try { IProjectDescription description = project.getDescription(); String[] natures = description.getNatureIds(); for (int i = 0; i < natures.length; ++i) { if (CALEclipseCorePlugin.NATURE_ID.equals(natures[i])) { // Remove the nature String[] newNatures = new String[natures.length - 1]; System.arraycopy(natures, 0, newNatures, 0, i); System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1); description.setNatureIds(newNatures); project.setDescription(description, null); return; } } CoreUtility.addCALNature(project, description, natures); } catch (CoreException e) { // Couldn't toggle the nature. // TODOEL: Handle this. CALEclipseUIPlugin.logException(e); } } public static boolean hasCALNature(IProject project){ try { return project.hasNature(CALEclipseCorePlugin.NATURE_ID); } catch (CoreException e) { // Couldn't toggle the nature. // TODOEL: Handle this. CALEclipseUIPlugin.logException(e); return false; } } /** * Create a new Eclipse project. * @param project The project to create * @param description A description for the new project * @param monitor A monitor for display progress * @throws CoreException */ private static void createProject(IProject project, IProjectDescription description, IProgressMonitor monitor) throws CoreException{ try{ monitor.beginTask("", 2000); project.create(description, new SubProgressMonitor(monitor, 1000)); project.open(new SubProgressMonitor(monitor, 1000)); } finally{ monitor.done(); } } public static SourceIdentifier.Category toCategory(Object object){ SourceIdentifier.Category category; if (object instanceof Function || object instanceof ClassMethod){ category = SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD; } else if (object instanceof TypeClass){ category = SourceIdentifier.Category.TYPE_CLASS; } else if (object instanceof TypeConstructor){ category = SourceIdentifier.Category.TYPE_CONSTRUCTOR; } else if (object instanceof DataConstructor){ category = SourceIdentifier.Category.DATA_CONSTRUCTOR; } else{ category = null; assert false; } return category; } /** * * TODO ADE this must change so that the reference will be in terms of classpath * variables, not the quark binary project * * Adds a reference to the Quark binaries project to the given project if needed. * @param projectNeedingReference project to add the reference to * @return true if the project either has a reference or a reference was added. False if there is no quark binaries project in the workspace. */ public static boolean addReferenceToQuarkBinariesIfNeeded(final IProject projectNeedingReference){ final CALModelManager cmm = CALModelManager.getCALModelManager(); // TODO ADE need some other way of finding the libraries // this will not work for the classpath variable final IStorage thisStorage = cmm.getInputSourceFile(CAL_Prelude.MODULE_NAME); if (! (thisStorage instanceof IFile)) { // No quark binaries project return false; } IFile thisFile = (IFile) thisStorage; final IProject thisProject = thisFile.getProject(); if (CALModelManager.dependsOnStatically(projectNeedingReference, thisProject)){ // already has a reference. return true; } // Not there so must add it try { final IProjectDescription projectDescription = projectNeedingReference.getDescription(); IProject[] referencedProjects = projectDescription.getReferencedProjects(); IProject[] newReferencedProjects = new IProject[referencedProjects.length + 1]; newReferencedProjects[0] = thisProject; System.arraycopy(referencedProjects, 0, newReferencedProjects, 1, referencedProjects.length); projectDescription.setReferencedProjects(newReferencedProjects); final ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(null); try { progressMonitorDialog.run(false, false, new IRunnableWithProgress() { public void run(IProgressMonitor monitor) { try{ projectNeedingReference.setDescription(projectDescription, progressMonitorDialog.getProgressMonitor()); monitor.done(); } catch (CoreException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ } }}); return true; } catch (InvocationTargetException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return false; } catch (InterruptedException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return false; } } catch (CoreException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return false; } } /** * Create a quark binary project in the current workspace. * @param errorMessageTitle Title for any error message dialogs * @param projectName The name of the project to make * @param locationOfQuarkBinaries The location of the quark binaries * @throws InvocationTargetException * @throws InterruptedException */ public static void createQuarkBinaryProject(final String errorMessageTitle, String projectName, String locationOfQuarkBinaries, Shell shell, IRunnableContext runnableContext) throws InvocationTargetException, InterruptedException{ final IWorkspace workspace = ResourcesPlugin.getWorkspace(); final IWorkspaceRoot workspaceRoot = workspace.getRoot(); final IProject project = workspaceRoot.getProject(projectName); final IProjectDescription description = workspace.newProjectDescription(project.getName()); final IPath location = new Path(locationOfQuarkBinaries); description.setLocation(location); WorkspaceModifyOperation op = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws CoreException { { // if the .project and .classpath files don't exist then make them. // .project file { IPath fromPath = location.append("eclipse-support").append(".project"); File fromFile = fromPath.toFile(); IPath toPath = location.append(".project"); File toFile = toPath.toFile(); try { if (!toFile.exists()){ copyFile(fromFile, toFile); } } catch (IOException e) { showMessage(errorMessageTitle, e); } } // .classpath file { IPath fromPath = location.append("eclipse-support").append(".classpath"); File fromFile = fromPath.toFile(); IPath toPath = location.append(".classpath"); File toFile = toPath.toFile(); try { if (!toFile.exists()){ copyFile(fromFile, toFile); } } catch (IOException e) { showMessage(errorMessageTitle, e); } } } createProject(project, description, monitor); } }; runnableContext.run(true, true, op); } private static void copyFile(File from, File to) throws IOException { FileInputStream inputStream = new FileInputStream(from); FileChannel inputChannel = inputStream.getChannel(); FileOutputStream outputStream = new FileOutputStream(to); FileChannel outputChannel = outputStream.getChannel(); // Copy file contents from source to destination outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); // Close the channels inputChannel.close(); outputChannel.force(true); outputChannel.close(); } public static void showCALElementInEditor(ScopedEntity calElement){ CALModelManager calModelManager = CALModelManager.getCALModelManager(); SourceMetricsManager smm = calModelManager.getSourceMetrics(); CompilerMessageLogger messageLogger = new MessageLogger(); List<SearchResult> results = smm.findDefinition(calElement.getName(), messageLogger); if (results != null && results.size() > 0){ // if there is more than one result pick the first one // TODO fix this when the search backend is updated. Precise result = (Precise) results.iterator().next(); IStorage definitionFile = calModelManager.getInputSourceFile(result.getName().getModuleName()); IEditorPart editorPart; try { editorPart = CoreUtility.openInEditor(definitionFile, true); } catch (PartInitException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ return; } CoreUtility.showPosition(editorPart, definitionFile, result.getSourceRange()); } else{ return; } } public static void showCALElementInEditor(ClassInstance classInstance){ CALModelManager calModelManager = CALModelManager.getCALModelManager(); SourceMetricsManager smm = calModelManager.getSourceMetrics(); CompilerMessageLogger messageLogger = new MessageLogger(); SourceRange position = smm.getPosition(classInstance, messageLogger); ModuleName moduleName = classInstance.getModuleName(); if (position != null){ // Open the editor to the correct position IStorage definitionFile = calModelManager.getInputSourceFile(moduleName); IEditorPart editorPart; try { editorPart = CoreUtility.openInEditor(definitionFile, true); CoreUtility.showPosition(editorPart, definitionFile, position); } catch (PartInitException e) { CALEclipseUIPlugin.log(new Status(IStatus.ERROR, CALEclipseUIPlugin.PLUGIN_ID, IStatus.OK, "", e)); //$NON-NLS-1$ } } else{ assert false; } } /** * Get argument names for the given type constructor. */ public static String[] getArgumentNamesFromCALDocComment(TypeConstructor typeConstructor) { CALDocComment caldoc = typeConstructor.getCALDocComment(); int nArgsInCALDoc = (caldoc == null) ? -1 : caldoc.getNArgBlocks(); int arity = typeConstructor.getTypeArity(); int nArgs = Math.max(nArgsInCALDoc, arity); if (nArgs > 0) { String argNames[] = new String[nArgs]; Set<String> setOfArgumentNames = new HashSet<String>(); for (int i = 0; i < nArgs; i++) { argNames[i] = getNthArgumentName(caldoc, null, i, setOfArgumentNames); } return argNames; } return new String[]{}; } private static String getNthArgumentName(CALDocComment caldoc, FunctionalAgent envEntity, int index, Set<String> setOfArgumentNames) { String baseArtificialName = "typeArg_" + (index + 1); String artificialName = baseArtificialName; // if the base artificial name already appears in previous arguments, then // make the argument name arg_x_y, where y is a supplementary disambiguating number // chosen so that the resulting name will not collide with any of the previous argument names int supplementaryDisambiguator = 1; while (setOfArgumentNames.contains(artificialName)) { artificialName = baseArtificialName + "_" + supplementaryDisambiguator; supplementaryDisambiguator++; } String result = artificialName; setOfArgumentNames.add(artificialName); return result; } }