/******************************************************************************* * Copyright (c) 2005, 2017 IBM Corporation and others. * 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 * *******************************************************************************/ package org.eclipse.dltk.internal.core; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.dltk.codeassist.ICompletionEngine; import org.eclipse.dltk.codeassist.ISelectionEngine; import org.eclipse.dltk.codeassist.ISelectionRequestor; import org.eclipse.dltk.compiler.env.IModuleSource; import org.eclipse.dltk.compiler.problem.DefaultProblem; import org.eclipse.dltk.compiler.problem.IProblemIdentifier; import org.eclipse.dltk.compiler.problem.ProblemSeverity; import org.eclipse.dltk.core.BufferChangedEvent; import org.eclipse.dltk.core.CompletionRequestor; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.IBuffer; import org.eclipse.dltk.core.IBufferChangedListener; import org.eclipse.dltk.core.ICompletionRequestorExtension; import org.eclipse.dltk.core.IDLTKLanguageToolkit; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IModelStatusConstants; import org.eclipse.dltk.core.IOpenable; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceRange; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.ScriptModelUtil; import org.eclipse.dltk.core.WorkingCopyOwner; /** * Abstract class for implementations of model elements which are IOpenable. * * @see IModelElement * @see IOpenable */ public abstract class Openable extends ModelElement implements IOpenable, IBufferChangedListener { protected Openable(ModelElement parent) { super(parent); } /** * The buffer associated with this element has changed. Registers this * element as being out of synch with its buffer's contents. If the buffer * has been closed, this element is set as NOT out of synch with the * contents. * * @see IBufferChangedListener */ @Override public void bufferChanged(BufferChangedEvent event) { if (event.getBuffer().isClosed()) { ModelManager.getModelManager().getElementsOutOfSynchWithBuffers() .remove(this); getBufferManager().removeBuffer(event.getBuffer()); } else { ModelManager.getModelManager().getElementsOutOfSynchWithBuffers() .add(this); } } /** * Builds this element's structure and properties in the given info object, * based on this element's current contents (reuse buffer contents if this * element has an open buffer, or resource contents if this element does not * have an open buffer). Children are placed in the given newElements table * (note, this element has already been placed in the newElements table). * Returns true if successful, or false if an error is encountered while * determining the structure of this element. */ protected abstract boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws ModelException; /* * Returns whether this element can be removed from the model cache to make * space. */ public boolean canBeRemovedFromCache() { try { return !hasUnsavedChanges(); } catch (ModelException e) { return false; } } /* * Returns whether the buffer of this element can be removed from the Script * model cache to make space. */ public boolean canBufferBeRemovedFromCache(IBuffer buffer) { return !buffer.hasUnsavedChanges(); } /** * Close the buffer associated with this element, if any. */ protected void closeBuffer() { if (!hasBuffer()) return; // nothing to do IBuffer buffer = getBufferManager().getBuffer(this); if (buffer != null) { buffer.close(); buffer.removeBufferChangedListener(this); } } /** * This element is being closed. Do any necessary cleanup. */ @Override protected void closing(Object info) { closeBuffer(); } /** * @see IModelElement */ @Override public boolean exists() { ModelManager manager = ModelManager.getModelManager(); if (manager.getInfo(this) != null) return true; if (!parentExists()) return false; IProjectFragment root = getProjectFragment(); if (root != null && (root == this || !root.isArchive())) { return resourceExists(); } return super.exists(); } @Override protected void generateInfos(Object info, HashMap newElements, IProgressMonitor monitor) throws ModelException { if (ModelManager.VERBOSE) { String element; switch (getElementType()) { case SCRIPT_PROJECT: element = "project"; //$NON-NLS-1$ break; case PROJECT_FRAGMENT: element = "fragment"; //$NON-NLS-1$ break; case SCRIPT_FOLDER: element = "folder"; //$NON-NLS-1$ break; case BINARY_MODULE: element = "binary module"; //$NON-NLS-1$ break; case SOURCE_MODULE: element = "source module"; //$NON-NLS-1$ break; default: element = "element"; //$NON-NLS-1$ } System.out.println(Thread.currentThread() + " OPENING " + element //$NON-NLS-1$ + " " + this.toStringWithAncestors()); //$NON-NLS-1$ } // open the parent if necessary openParent(info, newElements, monitor); if (monitor != null && monitor.isCanceled()) throw new OperationCanceledException(); // puts the info before building the structure so that questions to the // handle behave as if the element existed // (case of compilation units becoming working copies) newElements.put(this, info); // build the structure of the openable (this will open the buffer if // needed) try { OpenableElementInfo openableElementInfo = (OpenableElementInfo) info; boolean isStructureKnown = buildStructure(openableElementInfo, monitor, newElements, getResource()); openableElementInfo.setIsStructureKnown(isStructureKnown); } catch (ModelException e) { newElements.remove(this); throw e; } // remove out of sync buffer for this element ModelManager.getModelManager().getElementsOutOfSynchWithBuffers() .remove(this); if (ModelManager.VERBOSE) { System.out.println(ModelManager.getModelManager().cache .toStringFillingRation("-> ")); //$NON-NLS-1$ } } /** * Note: a buffer with no unsaved changes can be closed by the Model since * it has a finite number of buffers allowed open at one time. If this is * the first time a request is being made for the buffer, an attempt is made * to create and fill this element's buffer. If the buffer has been closed * since it was first opened, the buffer is re-created. * * @see IOpenable */ @Override public IBuffer getBuffer() throws ModelException { if (hasBuffer()) { // ensure element is open Object info = getElementInfo(); IBuffer buffer = getBufferManager().getBuffer(this); if (buffer == null) { // try to (re)open a buffer buffer = openBuffer(null, info); } return buffer; } else { return null; } } /** * Returns {@link IBuffer} or <code>null</code> if this element is not * opened yet. */ public IBuffer getBufferNotOpen() throws ModelException { if (hasBuffer()) { return getBufferManager().getBuffer(this); } return null; } /** * Returns the buffer manager for this element. */ protected BufferManager getBufferManager() { return BufferManager.getDefaultBufferManager(); } /** * Return my underlying resource. Elements that may not have a corresponding * resource must override this method. * * @see IScriptElement */ @Override public IResource getCorrespondingResource() throws ModelException { return getUnderlyingResource(); } /* * @see IModelElement */ @Override public IOpenable getOpenable() { return this; } @Override public IResource getUnderlyingResource() throws ModelException { IResource parentResource = this.parent.getUnderlyingResource(); if (parentResource == null) { return null; } int type = parentResource.getType(); if (type == IResource.FOLDER || type == IResource.PROJECT) { IContainer folder = (IContainer) parentResource; IResource resource = folder.findMember(getElementName()); if (resource == null) { throw newNotPresentException(); } else { return resource; } } else { return parentResource; } } /** * Returns true if this element may have an associated source buffer, * otherwise false. Subclasses must override as required. */ protected boolean hasBuffer() { return false; } /** * @see IOpenable */ @Override public boolean hasUnsavedChanges() throws ModelException { if (isReadOnly() || !isOpen()) { return false; } if (hasBuffer()) { IBuffer buf = this.getBufferNotOpen(); if (buf != null && buf.hasUnsavedChanges()) { return true; } } // for package fragments, package fragment roots, and projects must // check open buffers // to see if they have an child with unsaved changes int elementType = getElementType(); if (elementType == SCRIPT_FOLDER || elementType == PROJECT_FRAGMENT || elementType == SCRIPT_PROJECT || elementType == SCRIPT_MODEL) { // fix // for // 1FWNMHH Enumeration openBuffers = getBufferManager().getOpenBuffers(); while (openBuffers.hasMoreElements()) { IBuffer buffer = (IBuffer) openBuffers.nextElement(); if (buffer.hasUnsavedChanges()) { IModelElement owner = buffer.getOwner(); if (isAncestorOf(owner)) { return true; } } } } return false; } /** * Subclasses must override as required. * * @see IOpenable */ @Override public boolean isConsistent() { return true; } /** * * @see IOpenable */ @Override public boolean isOpen() { return ModelManager.getModelManager().getInfo(this) != null; } /** * Returns true if this represents a source element. Openable source * elements have an associated buffer created when they are opened. */ protected boolean isSourceElement() { return false; } /** * @see IModelElement */ @Override public boolean isStructureKnown() throws ModelException { return ((OpenableElementInfo) getElementInfo()).isStructureKnown(); } /** * @see IOpenable */ @Override public void makeConsistent(IProgressMonitor monitor) throws ModelException { // only source modules can be inconsistent // other openables cannot be inconsistent so default is to do nothing } /** * @see IOpenable */ @Override public void open(IProgressMonitor pm) throws ModelException { getElementInfo(pm); } /** * Opens a buffer on the contents of this element, and returns the buffer, * or returns <code>null</code> if opening fails. By default, do nothing - * subclasses that have buffers must override as required. */ protected IBuffer openBuffer(IProgressMonitor pm, Object info) throws ModelException { return null; } /** * Open the parent element if necessary. */ protected void openParent(Object childInfo, HashMap newElements, IProgressMonitor pm) throws ModelException { Openable openableParent = (Openable) getOpenableParent(); if (openableParent != null && !openableParent.isOpen()) { openableParent.generateInfos(openableParent.createElementInfo(), newElements, pm); } } /** * Answers true if the parent exists (null parent is answering true) * */ protected boolean parentExists() { IModelElement parentElement = getParent(); if (parentElement == null) return true; return parentElement.exists(); } /** * Returns whether the corresponding resource or associated file exists */ protected boolean resourceExists() { IWorkspace workspace = ResourcesPlugin.getWorkspace(); if (workspace == null) return false; // workaround for // http://bugs.eclipse.org/bugs/show_bug.cgi?id=34069 return Model.getTarget(workspace.getRoot(), this.getPath().makeRelative(), // ensure path is relative (see // http://dev.eclipse.org/bugs/show_bug.cgi?id=22517) true) != null; } /** * @see IOpenable */ @Override public void save(IProgressMonitor pm, boolean force) throws ModelException { if (isReadOnly()) { throw new ModelException( new ModelStatus(IModelStatusConstants.READ_ONLY, this)); } IBuffer buf = getBuffer(); if (buf != null) { // some Openables (like a ScriptProject) don't have // a // buffer buf.save(pm, force); this.makeConsistent(pm); // update the element info of this // element } } /** * Find enclosing project fragment if any */ public IProjectFragment getProjectFragment() { return (IProjectFragment) getAncestor(IModelElement.PROJECT_FRAGMENT); } static class CompletionThread extends Thread { final IDLTKLanguageToolkit toolkit; final IScriptProject project; final IModuleSource cu; final int position; final CompletionRequestor requestor; final NullProgressMonitor monitor = new NullProgressMonitor(); public CompletionThread(IDLTKLanguageToolkit toolkit, IScriptProject project, IModuleSource cu, int position, CompletionRequestor requestor) { super("CompletionThread-" + toolkit.getLanguageName()); this.toolkit = toolkit; this.project = project; this.cu = cu; this.position = position; this.requestor = requestor; } private boolean done = false; @Override public void run() { // code complete final ICompletionEngine[] engines = DLTKLanguageManager .getCompletionEngines(toolkit.getNatureId()); if (engines != null) { for (ICompletionEngine engine : engines) { run(engine); if (requestor.isIgnored(CompletionRequestor.ALL)) { break; } if (monitor.isCanceled()) break; } } done = true; } private void run(ICompletionEngine engine) { // engine.setEnvironment(environment); engine.setRequestor(requestor); engine.setOptions(project.getOptions(true)); engine.setProject(project); engine.setProgressMonitor(monitor); engine.complete(cu, position, 0); } boolean execute(long timeout) { start(); try { join(timeout); if (!done) { monitor.setCanceled(true); interrupt(); } return done; } catch (InterruptedException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } return false; } } } /** Code Completion */ protected void codeComplete(final IModuleSource cu, final int position, CompletionRequestor requestor, WorkingCopyOwner owner, long timeout) throws ModelException { if (requestor == null) { throw new IllegalArgumentException( Messages.Openable_completionRequesterCannotBeNull); } IBuffer buffer = getBuffer(); if (buffer == null) { return; } if (position < -1 || position > buffer.getLength()) { if (DLTKCore.DEBUG) { throw new ModelException(new ModelStatus( IModelStatusConstants.INDEX_OUT_OF_BOUNDS)); } return; } IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); if (toolkit == null) { return; } CompletionThread thread = new CompletionThread(toolkit, getScriptProject(), cu, position, requestor); if (!thread.execute(timeout)) { Thread.interrupted(); requestor.completionFailure(new DefaultProblem( "Computation of proposals takes too long. Please try again.", IProblemIdentifier.NULL, null, ProblemSeverity.WARNING, 0, 0, 0)); if (requestor instanceof ICompletionRequestorExtension) { ((ICompletionRequestorExtension) requestor).reset(); } } } static class ModelElementSelectionRequestor implements ISelectionRequestor { private final List<IModelElement> elements = new ArrayList<>(); private List<Object> foreignElements; private Map<Object, ISourceRange> ranges; @Override public void acceptForeignElement(Object element) { if (element instanceof IModelElement) { acceptModelElement((IModelElement) element); } else { if (foreignElements == null) { foreignElements = new ArrayList<>(); } foreignElements.add(element); } } @Override public void acceptModelElement(IModelElement element) { elements.add(element); } @Override public void acceptElement(Object element, ISourceRange range) { acceptForeignElement(element); if (range != null) { if (ranges == null) { ranges = new HashMap<>(); } ranges.put(element, range); } } void addModelElements(IModelElement[] elements) { Collections.addAll(this.elements, elements); } IModelElement[] toModelElementArray() { if (elements.isEmpty()) { return ScriptModelUtil.NO_ELEMENTS; } else { return elements.toArray(new IModelElement[elements.size()]); } } boolean isEmpty() { return elements.isEmpty() && (foreignElements == null || foreignElements.isEmpty()); } CodeSelection asResponse() { if (isEmpty()) { return null; } else { final IModelElement[] elementArray = elements != null && !elements.isEmpty() ? elements.toArray( new IModelElement[elements.size()]) : null; final Object[] foreignElementArray = foreignElements != null && !foreignElements.isEmpty() ? foreignElements.toArray() : null; return new CodeSelection(elementArray, foreignElementArray, ranges); } } } private void codeSelect(org.eclipse.dltk.compiler.env.IModuleSource cu, int offset, int length, WorkingCopyOwner owner, ModelElementSelectionRequestor requestor) throws ModelException { ScriptProject project = (ScriptProject) getScriptProject(); IBuffer buffer = getBuffer(); int end = -1; if (buffer != null) { end = buffer.getLength(); } IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); if (toolkit == null) { if (DLTKCore.VERBOSE) { System.out.println( "DLTK.Openable.VERBOSE: Failed to detect language toolkit... for module:" //$NON-NLS-1$ + this.getResource().getName()); } return; } if (offset < 0 || length < 0 || (end != -1 && (offset + length > end))) { throw new ModelException( new ModelStatus(IModelStatusConstants.INDEX_OUT_OF_BOUNDS)); } final ISelectionEngine[] engines = DLTKLanguageManager .getSelectionEngines(toolkit.getNatureId()); if (engines != null) { for (ISelectionEngine engine : engines) { engine.setOptions(project.getOptions(true)); engine.setRequestor(requestor); final IModelElement[] result = engine.select(cu, offset, offset + length - 1); if (result != null) { requestor.addModelElements(result); } if (!requestor.isEmpty()) { return; } } } } protected IModelElement[] codeSelect( org.eclipse.dltk.compiler.env.IModuleSource cu, int offset, int length, WorkingCopyOwner owner) throws ModelException { final ModelElementSelectionRequestor requestor = new ModelElementSelectionRequestor(); codeSelect(cu, offset, length, owner, requestor); return requestor.toModelElementArray(); } protected CodeSelection codeSelectAll( org.eclipse.dltk.compiler.env.IModuleSource cu, int offset, int length, WorkingCopyOwner owner) throws ModelException { final ModelElementSelectionRequestor requestor = new ModelElementSelectionRequestor(); codeSelect(cu, offset, length, owner, requestor); return requestor.asResponse(); } }