/******************************************************************************* * Copyright (c) 2016, 2017 xored software, Inc. 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 * * Contributors: * xored software, Inc. - initial API and Implementation (Alex Panchenko) *******************************************************************************/ package org.eclipse.dltk.internal.core; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.dltk.annotations.Nullable; import org.eclipse.dltk.compiler.CharOperation; import org.eclipse.dltk.compiler.env.IModuleSource; import org.eclipse.dltk.core.BufferChangedEvent; import org.eclipse.dltk.core.CompletionRequestor; import org.eclipse.dltk.core.DLTKContentTypeManager; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.IBuffer; import org.eclipse.dltk.core.IDLTKLanguageToolkit; import org.eclipse.dltk.core.IField; import org.eclipse.dltk.core.IMethod; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IModelProvider; import org.eclipse.dltk.core.IModelStatus; import org.eclipse.dltk.core.IModelStatusConstants; import org.eclipse.dltk.core.IPackageDeclaration; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceElementParser; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ISourceRange; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.RuntimePerformanceMonitor; import org.eclipse.dltk.core.RuntimePerformanceMonitor.PerformanceNode; import org.eclipse.dltk.core.WorkingCopyOwner; import org.eclipse.dltk.internal.core.ModelManager.PerWorkingCopyInfo; import org.eclipse.dltk.internal.core.util.MementoTokenizer; import org.eclipse.dltk.internal.core.util.Messages; import org.eclipse.dltk.internal.core.util.Util; import org.eclipse.dltk.utils.CorePrinter; public abstract class AbstractSourceModule extends Openable implements ISourceModule, IModuleSource { private static final int DEFAULT_COMPLETION_TIMEOUT = 5000; // ~ Static fields/initializers private static final boolean DEBUG_PRINT_MODEL = DLTKCore.DEBUG_PRINT_MODEL; // ~ Instance fields private final String name; private WorkingCopyOwner owner; // ~ Constructors protected AbstractSourceModule(ModelElement parent, String name, WorkingCopyOwner owner) { super(parent); this.name = name; this.owner = owner; } // ~ Methods @Override public IModelElement[] codeSelect(int offset, int length) throws ModelException { return codeSelect(offset, length, DefaultWorkingCopyOwner.PRIMARY); } @Override public IModelElement[] codeSelect(int offset, int length, WorkingCopyOwner owner) throws ModelException { return super.codeSelect(this, offset, length, owner); } @Override public CodeSelection codeSelectAll(int offset, int length) throws ModelException { return codeSelectAll(offset, length, DefaultWorkingCopyOwner.PRIMARY); } @Override public CodeSelection codeSelectAll(int offset, int length, WorkingCopyOwner owner) throws ModelException { return super.codeSelectAll(this, offset, length, owner); } @Override public void copy(IModelElement container, IModelElement sibling, String rename, boolean replace, IProgressMonitor monitor) throws ModelException { if (container == null) { throw new IllegalArgumentException( Messages.operation_nullContainer); } IModelElement[] elements = new IModelElement[] { this }; IModelElement[] containers = new IModelElement[] { container }; String[] renamings = null; if (rename != null) { renamings = new String[] { rename }; } getModel().copy(elements, containers, null, renamings, replace, monitor); } // ~ Methods @Override public boolean equals(Object obj) { if (obj instanceof AbstractSourceModule) { AbstractSourceModule other = (AbstractSourceModule) obj; return this.owner.equals(other.owner) && super.equals(obj); } return false; } @Override public boolean exists() { // if not a working copy, it exists only if it is a primary compilation // unit return isPrimary() && validateSourceModule(getResource()).isOK(); } @Override public IType[] getAllTypes() throws ModelException { IType[] types = getTypes(); int i; ArrayList<IType> allTypes = new ArrayList<>(types.length); ArrayList<IType> typesToTraverse = new ArrayList<>(types.length); for (i = 0; i < types.length; i++) { typesToTraverse.add(types[i]); } while (!typesToTraverse.isEmpty()) { IType type = typesToTraverse.get(0); typesToTraverse.remove(type); allTypes.add(type); types = type.getTypes(); for (i = 0; i < types.length; i++) { typesToTraverse.add(types[i]); } } IType[] arrayOfAllTypes = new IType[allTypes.size()]; allTypes.toArray(arrayOfAllTypes); return arrayOfAllTypes; } @Override public IModelElement getElementAt(int position) throws ModelException { IModelElement e = getSourceElementAt(position); if (e == this) { return null; } return e; } @Override public String getElementName() { return name; } @Override public int getElementType() { return SOURCE_MODULE; } @Override public IField getField(String fieldName) { return new SourceField(this, fieldName); } @Override public IField[] getFields() throws ModelException { List<IModelElement> list = getChildrenOfType(FIELD); IField[] array = new IField[list.size()]; list.toArray(array); return array; } @Override public IModelElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner workingCopyOwner) { switch (token.charAt(0)) { case JEM_IMPORTDECLARATION: { if (DLTKCore.DEBUG) { System.err.println( "Add import support in SourceModule getHandleFromMemento"); //$NON-NLS-1$ } // ModelElement container = (ModelElement)getImportContainer(); // return container.getHandleFromMemento(token, memento, // workingCopyOwner); break; } case JEM_PACKAGEDECLARATION: { if (!memento.hasMoreTokens()) { return this; } String pkgName = memento.nextToken(); ModelElement pkgDecl = (ModelElement) getPackageDeclaration( pkgName); return pkgDecl.getHandleFromMemento(memento, workingCopyOwner); } case JEM_TYPE: { if (!memento.hasMoreTokens()) { return this; } String typeName = memento.nextToken(); ModelElement type = (ModelElement) getType(typeName); return type.getHandleFromMemento(memento, workingCopyOwner); } case JEM_METHOD: { if (!memento.hasMoreTokens()) { return this; } String methodName = memento.nextToken(); ModelElement method = (ModelElement) getMethod(methodName); return method.getHandleFromMemento(memento, workingCopyOwner); } case JEM_FIELD: { if (!memento.hasMoreTokens()) { return this; } String field = memento.nextToken(); ModelElement fieldE = (ModelElement) getField(field); return fieldE.getHandleFromMemento(memento, workingCopyOwner); } case JEM_USER_ELEMENT: return MementoModelElementUtil.getHandleFromMemento(memento, this, owner); } return null; } @Override public IMethod getMethod(String selector) { return new SourceMethod(this, selector); } public IMethod[] getMethods() throws ModelException { List<IModelElement> list = getChildrenOfType(METHOD); IMethod[] array = new IMethod[list.size()]; list.toArray(array); return array; } @Override public IModelElement getModelElement() { return this; } @Override public WorkingCopyOwner getOwner() { return (isPrimary() || !isWorkingCopy()) ? null : this.owner; } @Override public IPackageDeclaration getPackageDeclaration(String pkg) { return new PackageDeclaration(this, pkg); } @Override public IPackageDeclaration[] getPackageDeclarations() throws ModelException { List<IModelElement> list = getChildrenOfType(PACKAGE_DECLARATION); IPackageDeclaration[] array = new IPackageDeclaration[list.size()]; list.toArray(array); return array; } @Override public IPath getPath() { IProjectFragment root = this.getProjectFragment(); // allow the root to be null for remote source if (root != null && root.isArchive()) { return root.getPath(); } return this.getParent().getPath().append(this.getElementName()); } @Override public ISourceModule getPrimary() { return (ISourceModule) getPrimaryElement(true); } @Override public IModelElement getPrimaryElement(boolean checkOwner) { if (checkOwner && isPrimary()) { return this; } return getOriginalSourceModule(); } @Override public String getSource() throws ModelException { IBuffer buffer = getBufferNotOpen(); if (buffer == null) return new String(getBufferContent()); return buffer.getContents(); } @Override public char[] getSourceAsCharArray() throws ModelException { IBuffer buffer = getBufferNotOpen(); if (buffer == null) return getBufferContent(); return buffer.getContents().toCharArray(); // return getSource().toCharArray(); } @Override public String getSourceContents() { try { return getSource(); } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } return org.eclipse.dltk.compiler.util.Util.EMPTY_STRING; } } @Override public char[] getContentsAsCharArray() { try { return getSourceAsCharArray(); } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } return CharOperation.NO_CHAR; } } @Override public ISourceModule getSourceModule() { return this; } @Override public ISourceRange getSourceRange() throws ModelException { return ((SourceModuleElementInfo) getElementInfo()).getSourceRange(); } @Override public IType getType(String typeName) { return new SourceType(this, typeName); } @Override public IType[] getTypes() throws ModelException { List<IModelElement> list = getChildrenOfType(TYPE); IType[] array = new IType[list.size()]; list.toArray(array); return array; } @Override public IResource getUnderlyingResource() throws ModelException { if (isWorkingCopy() && !isPrimary()) { return null; } return super.getUnderlyingResource(); } @Override public ISourceModule getWorkingCopy(IProgressMonitor monitor) throws ModelException { return getWorkingCopy( new WorkingCopyOwner() /* * non shared working copy */ { }, null /* no problem requestor */, monitor); } @Override public boolean isBuiltin() { return false; } @Override public boolean isConsistent() { return !ModelManager.getModelManager() .getElementsOutOfSynchWithBuffers().contains(this); } @Override public void bufferChanged(BufferChangedEvent event) { super.bufferChanged(event); if (!event.getBuffer().isClosed()) { ModelManager.getModelManager().getSourceModuleInfoCache() .remove(this); } } @Override public boolean isPrimary() { return this.owner == DefaultWorkingCopyOwner.PRIMARY; } @Override public void printNode(CorePrinter output) { output.formatPrint(getModuleType() + ": " + getElementName()); output.indent(); try { IModelElement[] modelElements = this.getChildren(); for (int i = 0; i < modelElements.length; ++i) { IModelElement element = modelElements[i]; if (element instanceof ModelElement) { ((ModelElement) element).printNode(output); } else { output.print("Unknown element:" + element); //$NON-NLS-1$ } } } catch (ModelException ex) { output.formatPrint(ex.getLocalizedMessage()); } output.dedent(); } protected abstract char[] getBufferContent() throws ModelException; protected String getModuleType() { return getClass().getSimpleName(); } @Nullable protected abstract String getNatureId(); protected abstract ISourceModule getOriginalSourceModule(); protected ModelManager.PerWorkingCopyInfo getPerWorkingCopyInfo() { return null; } /** * Returns {@link AccumulatingProblemReporter} or <code>null</code> * * @return */ private AccumulatingProblemReporter getAccumulatingProblemReporter() { final PerWorkingCopyInfo perWorkingCopyInfo = getPerWorkingCopyInfo(); if (perWorkingCopyInfo != null && perWorkingCopyInfo.isActive() && !isReadOnly()) { // Always report problems to AnnotationModel (in // SourceModuleDocumentProvider). Should be useful for // editing some scripts in other project without setting correctly // the natures. return new AccumulatingProblemReporter(this, perWorkingCopyInfo); } return null; } @Override protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws ModelException { // check if this source module can be opened if (!isWorkingCopy()) { // no check is done on root kind or // exclusion pattern for working copies final IStatus status = validateSourceModule(underlyingResource); if (!status.isOK()) { throw newModelException(status); } } // prevents reopening of non-primary working copies (they are closed // when they are discarded and should not be reopened) if (preventReopen()) { throw newNotPresentException(); } final SourceModuleElementInfo moduleInfo = (SourceModuleElementInfo) info; // ensure buffer is opened if (hasBuffer()) { final IBuffer buffer = getBufferManager().getBuffer(this); if (buffer == null) { openBuffer(pm, moduleInfo); } } // generate structure and compute syntax problems if needed final SourceModuleStructureRequestor requestor = new SourceModuleStructureRequestor( this, moduleInfo, newElements); // System.out.println("==> Parsing: " + resource.getName()); final String natureId = getNatureId(); if (natureId == null) { throw new ModelException(new ModelStatus(ModelStatus.INVALID_NAME)); } final ISourceElementParser parser = getSourceElementParser(natureId); final AccumulatingProblemReporter problemReporter = getAccumulatingProblemReporter(); if (parser != null) { parser.setRequestor(requestor); parser.setReporter(problemReporter); PerformanceNode p = RuntimePerformanceMonitor.begin(); parser.parseSourceModule(this); p.done(natureId, "Source Element parser", 0); } if (problemReporter != null) { new ReconcileBuilder(natureId, this, problemReporter).build(); problemReporter.reportToRequestor(); } if (DEBUG_PRINT_MODEL) { System.out.println("Source Module Debug print:"); //$NON-NLS-1$ CorePrinter printer = new CorePrinter(System.out); printNode(printer); printer.flush(); } // update timestamp (might be IResource.NULL_STAMP if original does // not exist) if (underlyingResource == null) { underlyingResource = getResource(); } // underlying resource is null in the case of a working copy out of // workspace if (underlyingResource != null) { moduleInfo.timestamp = ((IFile) underlyingResource) .getModificationStamp(); } // We need to update children contents using model providers // Call for extra model providers final IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(this); final IModelProvider[] providers = ModelProviderManager .getProviders(toolkit.getNatureId()); if (providers != null) { final List<IModelElement> childrenSet = new ArrayList<>( moduleInfo.getChildrenAsList()); for (int i = 0; i < providers.length; i++) { providers[i].provideModelChanges(this, childrenSet); } moduleInfo.setChildren(childrenSet); } return moduleInfo.isStructureKnown(); } @Override protected Object createElementInfo() { return new SourceModuleElementInfo(); } @Override protected char getHandleMementoDelimiter() { return JEM_SOURCEMODULE; } protected ISourceElementParser getSourceElementParser(String natureId) { return DLTKLanguageManager.getSourceElementParser(natureId); } @Override protected boolean hasBuffer() { return true; } protected final IDLTKLanguageToolkit lookupLanguageToolkit(Object object) { IDLTKLanguageToolkit toolkit = null; if (object instanceof IPath) { toolkit = DLTKLanguageManager.findToolkit((IPath) object); } else if (object instanceof IResource) { toolkit = DLTKLanguageManager.findToolkit(getParent(), (IResource) object, true); } else if (object instanceof IScriptProject) { toolkit = DLTKLanguageManager .getLanguageToolkit((IScriptProject) object); } else if (object instanceof IModelElement) { toolkit = DLTKLanguageManager .getLanguageToolkit((IModelElement) object); } return toolkit; } /* * @see org.eclipse.dltk.internal.core.Openable#openBuffer(org.eclipse.core. * runtime .IProgressMonitor, java.lang.Object) */ @Override protected IBuffer openBuffer(IProgressMonitor pm, Object info) throws ModelException { // create buffer final BufferManager bufManager = getBufferManager(); final boolean isWorkingCopy = isWorkingCopy(); IBuffer buffer = isWorkingCopy ? this.owner.createBuffer(this) : BufferManager.createBuffer(this); if (buffer == null) { return null; } ISourceModule original = null; boolean mustSetToOriginalContent = false; if (isWorkingCopy) { // ensure that isOpen() is called outside the bufManager // synchronized block // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=237772 mustSetToOriginalContent = !isPrimary() && (original = getOriginalSourceModule()).isOpen(); } /* * synchronize to ensure that 2 threads are not putting 2 different * buffers at the same time see * https://bugs.eclipse.org/bugs/show_bug.cgi?id=146331 */ synchronized (bufManager) { final IBuffer existingBuffer = bufManager.getBuffer(this); if (existingBuffer != null) return existingBuffer; // set the buffer source if (buffer.getCharacters() == null) { if (isWorkingCopy) { if (mustSetToOriginalContent) { buffer.setContents(original.getSource()); } else { char[] content; try { content = getBufferContent(); } catch (ModelException e) { if (e.isDoesNotExist()) { content = CharOperation.NO_CHAR; } else { throw e; } } buffer.setContents(content); } } else { char[] content = getBufferContent(); buffer.setContents(content); } } // add buffer to buffer cache /* * note this may cause existing buffers to be removed from the * buffer cache, but only primary compilation unit's buffer can be * closed, thus no call to a client's IBuffer#close() can be done in * this synchronized block. */ bufManager.addBuffer(buffer); // listen to buffer changes buffer.addBufferChangedListener(this); } return buffer; } @Override protected void openParent(Object childInfo, HashMap newElements, IProgressMonitor pm) throws ModelException { if (!isWorkingCopy()) { super.openParent(childInfo, newElements, pm); } // don't open parent for a working copy to speed up the first // becomeWorkingCopy } protected boolean preventReopen() { return !isPrimary(); } protected IStatus validateSourceModule(IResource resource) { IProjectFragment root = getProjectFragment(); try { if (root.getKind() != IProjectFragment.K_SOURCE) { return new ModelStatus( IModelStatusConstants.INVALID_ELEMENT_TYPES, root); } } catch (ModelException e) { return e.getModelStatus(); } if (resource != null) { if (Util.isExcluded(resource, root)) return new ModelStatus( IModelStatusConstants.ELEMENT_NOT_ON_BUILDPATH, this); if (!resource.isAccessible()) return new ModelStatus( IModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this); } IDLTKLanguageToolkit toolkit = null; if (!root.isArchive()) { toolkit = lookupLanguageToolkit(this); } IStatus status = validateSourceModule(toolkit, resource); if (status != null) { return status; } return new ModelStatus(IModelStatusConstants.INVALID_RESOURCE, root); } protected IStatus validateSourceModule(IDLTKLanguageToolkit toolkit, IResource resource) { if (toolkit == null) { toolkit = DLTKLanguageManager.findToolkit(getParent(), resource, true); } if (toolkit != null) { if (DLTKContentTypeManager.isValidResourceForContentType(toolkit, resource)) { return IModelStatus.VERIFIED_OK; } } return null; } @Override public void codeComplete(int offset, CompletionRequestor requestor) throws ModelException { codeComplete(offset, requestor, DefaultWorkingCopyOwner.PRIMARY, DEFAULT_COMPLETION_TIMEOUT); } @Override public void codeComplete(int offset, CompletionRequestor requestor, long timeout) throws ModelException { codeComplete(offset, requestor, DefaultWorkingCopyOwner.PRIMARY, timeout); } @Override public void codeComplete(int offset, CompletionRequestor requestor, WorkingCopyOwner owner, long timeout) throws ModelException { codeComplete(this, offset, requestor, owner, timeout); } @Override public void codeComplete(int offset, CompletionRequestor requestor, WorkingCopyOwner owner) throws ModelException { codeComplete(this, offset, requestor, owner, DEFAULT_COMPLETION_TIMEOUT); } @Override public boolean isBinary() { return false; } @Override public ISourceRange getNameRange() throws ModelException { return null; } }