--- a/parsing.api/apichanges.xml +++ a/parsing.api/apichanges.xml @@ -110,6 +110,68 @@ + + + Source provides access to Lookup + + + + + +

+ In the presence of multiple scopes or users, the parsing system needs + to access the proper context even though the work is done by different + threads. It also has to propagate the proper context back to the user + tasks. +

+

+ Source can now be created with a Lookup that provides such proper context, + or automatically uses Lookup.getDefault if no explicit Lookup is given. +

+
+ +
+ + + Parsing API cleaned up from IDE dependencies + + + + + +

+ The Parsing API dependended on Windows System, Editor Registry, Data Systems and other IDE artifacts. All those + dependencies were externalized as 'private SPIs' in the package org.netbeans.modules.parsing.impspi. + The implementation that uses the other NetBeans IDE APIs resides in module parsing.nb. + No API change was done, but the execution now relies on a proper binding to be present in the running application + or a testsuite. +

+

+ Basic testing environment was provided in unit tests, which implements basic of the externalized functions. However + if a test need to check reparsing after document change, or data object change. Full implementation may be needed - + the implementor can use a test-dependency to the org.netbeans.modules.parsing.nb module. +

+
+
+ + + Indexing API separated to its own module + + + + + +

+ Historically the module offered both APIs for Parsing (invoking parser, implementing parser) and + Indexing (refresh index, index SPIs). Those APIs are typically used by unrelated pieces of code, + possibly with the exception of "refresh" actions. +

+

+ The indexing now resides separately in its parsing.indexing module. Binary compatibility + is kept by using module-auto-deps.xml. +

+
+
Added SuspendStatus.isSuspendSupported --- a/parsing.api/manifest.mf +++ a/parsing.api/manifest.mf @@ -1,7 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.parsing.api/1 OpenIDE-Module-Implementation-Version: 8 -OpenIDE-Module-Install: org/netbeans/modules/parsing/impl/Installer.class OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/parsing/impl/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/parsing/impl/layer.xml AutoUpdate-Show-In-Client: false --- a/parsing.api/module-auto-deps.xml +++ a/parsing.api/module-auto-deps.xml @@ -0,0 +1,63 @@ + + + + + + + + Separation of Parsing and Indexing APIs + + + + + + + + + + + + --- a/parsing.api/nbproject/project.properties +++ a/parsing.api/nbproject/project.properties @@ -3,7 +3,7 @@ javac.source=1.7 javadoc.apichanges=${basedir}/apichanges.xml javadoc.arch=${basedir}/arch.xml -spec.version.base=1.78.0 +spec.version.base=9.2.0 test.config.stableBTD.includes=**/*Test.class test.config.stableBTD.excludes=\ --- a/parsing.api/nbproject/project.xml +++ a/parsing.api/nbproject/project.xml @@ -24,39 +24,11 @@ - org.netbeans.api.progress + org.netbeans.modules.editor.document - 1 - 1.13 - - - - org.netbeans.libs.lucene - - - - 3 - 3.0 - - - - org.netbeans.modules.editor.lib - - - - 3 - 3.1 - - - - org.netbeans.modules.editor.lib2 - - - - 1 - 1.13 + 1.1 @@ -69,15 +41,6 @@ - org.netbeans.modules.editor.settings.storage - - - - 1 - 1.19 - - - org.netbeans.modules.editor.util @@ -96,29 +59,12 @@ - org.netbeans.modules.masterfs - - - - 2 - 2.25 - - - org.netbeans.modules.parsing.lucene 2 - 2.24 - - - - org.netbeans.modules.project.indexingbridge - - - - 1.5 + 2.28 @@ -131,15 +77,6 @@ - org.netbeans.modules.projectuiapi - - - - 1 - 1.32 - - - org.netbeans.modules.queries @@ -149,44 +86,11 @@ - org.netbeans.modules.sampler - - - - 1.0 - - - - org.netbeans.spi.tasklist - - - - 1 - 1.10 - - - - org.openide.awt - - - - 7.25 - - - org.openide.filesystems - 7.61 - - - - org.openide.loaders - - - - 6.9 + 9.0 @@ -198,27 +102,11 @@ - org.openide.nodes + org.openide.util.base - 7.3 - - - - org.openide.text - - - - 6.17 - - - - org.openide.util - - - - 8.32 + 9.0 @@ -229,14 +117,6 @@ 8.0 - - org.openide.windows - - - - 6.20 - - @@ -277,6 +157,9 @@ + org.netbeans.modules.projectapi.nb + + org.netbeans.modules.projectui @@ -290,14 +173,19 @@ + + + org.netbeans.api.progress.nb + org.netbeans.modules.parsing.api - org.netbeans.modules.parsing.api.indexing org.netbeans.modules.parsing.spi - org.netbeans.modules.parsing.spi.indexing - org.netbeans.modules.parsing.spi.indexing.support org.netbeans.modules.parsing.spi.support --- a/parsing.api/src/org/netbeans/modules/parsing/api/ParserManager.java +++ a/parsing.api/src/org/netbeans/modules/parsing/api/ParserManager.java @@ -43,8 +43,6 @@ package org.netbeans.modules.parsing.api; import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -52,10 +50,10 @@ import java.util.Map; import java.util.concurrent.Future; import org.netbeans.api.annotations.common.NonNull; - +import org.netbeans.api.editor.document.EditorMimeTypes; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.modules.parsing.impl.*; -import org.netbeans.modules.parsing.lucene.support.LowMemoryWatcher; +import org.netbeans.modules.parsing.spi.LowMemoryWatcher; import org.netbeans.modules.parsing.spi.ParseException; import org.netbeans.modules.parsing.spi.Parser; import org.netbeans.modules.parsing.spi.ParserFactory; @@ -390,22 +388,73 @@ } private static Parser findParser (final String mimeType) { - Parser p = null; - final Reference ref = cachedParsers.get (mimeType); - if (ref != null) { - p = ref.get(); + return Utilities.getEnvFactory().findMimeParser(Lookup.getDefault(), mimeType); + } + + /** + * Determines if the current execution is already called from the parsing + * system. If code is called by the parser, it should avoid blocking on a lock + * and must avoid blocking on other parsing results. + * + * @return true, if the current thread executes code called from within the parser + * @since 9.2 + */ + public static boolean isParsing() { + return Utilities.holdsParserLock(); + } + + /** + * Determines if the MIME type can be parsed. Rejects unknown MIME types (must be amongst {@link #getAllMimeTypes}. + * Then accepts only text/* MIME type and specific hardcoded application/ MIME types. + * + * @param mimeType the MIME type to check + * @return true, if the MIME type can be parsed. + * @since 9.2 + */ + public static boolean canBeParsed(String mimeType) { + if (mimeType == null || + "content/unknown".equals(mimeType) || //NOI18N + !EditorMimeTypes.getDefault().getSupportedMimeTypes().contains(mimeType)) { + return false; } - if (p == null) { - final Lookup lookup = MimeLookup.getLookup (mimeType); - final ParserFactory parserFactory = lookup.lookup (ParserFactory.class); - if (parserFactory == null) { - throw new IllegalArgumentException("No parser for mime type: " + mimeType); + + int slashIdx = mimeType.indexOf('/'); //NOI18N + assert slashIdx != -1 : "Invalid mimetype: '" + mimeType + "'"; //NOI18N + + String type = mimeType.substring(0, slashIdx); + if (type.equals("application")) { //NOI18N + if (!mimeType.equals("application/x-httpd-eruby") && !mimeType.equals("application/xml-dtd")) { //NOI18N + return false; } - p = parserFactory.createParser(Collections.emptyList()); - cachedParsers.put(mimeType, new SoftReference(p)); + } else if (!type.equals("text")) { //NOI18N + return false; } - return p; + +// if (allLanguagesParsersCount == -1) { +// Collection allLanguagesParsers = MimeLookup.getLookup(MimePath.EMPTY).lookupAll(ParserFactory.class); +// allLanguagesParsersCount = allLanguagesParsers.size(); +// } +// Collection parsers = MimeLookup.getLookup(mimeType).lookupAll(ParserFactory.class); +// if (parsers.size() - allLanguagesParsersCount > 0) { +// return true; +// } +// +// // Ideally we should check that there are EmbeddingProviders registered for the +// // mimeType, but let's assume that if there are TaskFactories they are either +// // ordinary scheduler tasks or EmbeddingProviders. The former would most likely +// // mean that there is also a Parser and would have been caught in the previous check. +// if (allLanguagesTasksCount == -1) { +// Collection allLanguagesTasks = MimeLookup.getLookup(MimePath.EMPTY).lookupAll(TaskFactory.class); +// allLanguagesTasksCount = allLanguagesTasks.size(); +// } +// Collection tasks = MimeLookup.getLookup(mimeType).lookupAll(TaskFactory.class); +// if (tasks.size() - allLanguagesTasksCount > 0) { +// return true; +// } + + return true; } + //where private static Map> cachedParsers = new HashMap>(); } --- a/parsing.api/src/org/netbeans/modules/parsing/api/Snapshot.java +++ a/parsing.api/src/org/netbeans/modules/parsing/api/Snapshot.java @@ -53,7 +53,7 @@ import org.netbeans.api.lexer.Language; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenId; -import org.netbeans.modules.parsing.impl.Installer; +import org.netbeans.modules.parsing.impl.Utilities; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; @@ -112,7 +112,7 @@ int[][] currentToOriginal, int[][] originalToCurrent) { final int textLength = text.length(); - if (textLength > Installer.MAX_FILE_SIZE) { + if (textLength > Utilities.getMaxFileSize()) { text = ""; //NOI18N LOG.log( Level.WARNING, @@ -123,7 +123,7 @@ FileUtil.getFileDisplayName(source.getFileObject()), mimePath, textLength, - Installer.MAX_FILE_SIZE + Utilities.getMaxFileSize() }); } return new Snapshot( --- a/parsing.api/src/org/netbeans/modules/parsing/api/Source.java +++ a/parsing.api/src/org/netbeans/modules/parsing/api/Source.java @@ -73,25 +73,24 @@ import org.netbeans.api.lexer.LanguagePath; import org.netbeans.api.queries.FileEncodingQuery; import org.netbeans.lib.editor.util.swing.DocumentUtilities; -import org.netbeans.modules.parsing.impl.Installer; +import org.netbeans.modules.parsing.impl.ParserEventForward; import org.netbeans.modules.parsing.impl.SchedulerAccessor; import org.netbeans.modules.parsing.impl.SourceAccessor; import org.netbeans.modules.parsing.impl.SourceCache; import org.netbeans.modules.parsing.impl.SourceFlags; import org.netbeans.modules.parsing.impl.TaskProcessor; -import org.netbeans.modules.parsing.impl.event.EventSupport; -import org.netbeans.modules.parsing.impl.indexing.Util; +import org.netbeans.modules.parsing.impl.Utilities; +import org.netbeans.modules.parsing.implspi.SchedulerControl; +import org.netbeans.modules.parsing.implspi.SourceControl; +import org.netbeans.modules.parsing.implspi.SourceEnvironment; import org.netbeans.modules.parsing.spi.Parser; import org.netbeans.modules.parsing.spi.Scheduler; import org.netbeans.modules.parsing.spi.SchedulerEvent; import org.netbeans.modules.parsing.spi.SourceModificationEvent; -import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; -import org.openide.loaders.DataObject; -import org.openide.loaders.DataObjectNotFoundException; +import org.openide.util.Lookup; import org.openide.util.Parameters; -import org.openide.util.UserQuestionException; /** @@ -109,12 +108,18 @@ * calls for the same file or document will return two different Source * instances if the first instance is garbage collected prior the second call. * + *

+ * The Source can be created with a {@link Lookup} instance. This Lookup should be used + * by parsing system, or callback tasks to locate services, that depend on the execution + * context (ie which user is executing the task). Check the specific service's documentation + * on details of the service's context dependencies. * * @author Jan Jancura * @author Tomas Zezula + * @since 9.2 implements Lookup.Provider */ -public final class Source { - +public final class Source implements Lookup.Provider { + /** * Gets a Source instance for a file. The FileObject * passed to this method has to be a valid data file. There is no Source @@ -134,10 +139,36 @@ return null; } - return _get(fileObject.getMIMEType(), fileObject); + return _get(fileObject.getMIMEType(), fileObject, Lookup.getDefault()); } /** + * Gets a Source instance for a file. The FileObject + * passed to this method has to be a valid data file. There is no Source + * representation for a folder. + *

+ * This form allows to specify Lookup that provides access to context-dependent + * services for the parsing system and the user tasks called from parsing API. + * + * @param fileObject The file to get Source for. + * @param lkp The Lookup that provides the context + * @return The Source for the given file or null + * if the file doesn't exist. + * @since 9.2 + */ + public static Source create ( + FileObject fileObject, Lookup lkp + ) { + Parameters.notNull("fileObject", fileObject); //NOI18N + Parameters.notNull("lkp", lkp); //NOI18N + if (!fileObject.isValid() || !fileObject.isData()) { + return null; + } + + return _get(fileObject.getMIMEType(), fileObject, lkp); + } + + /** * Gets a Source instance for a Document. This method * is consistent with {@link #create(org.openide.filesystems.FileObject)} in the way * that they both will return the same Source instance for @@ -171,6 +202,23 @@ public static Source create ( Document document ) { + return create(document, Lookup.getDefault()); + } + + /** + * Gets a Source instance for a Document. For details, please + * see {@link #create(javax.swing.text.Document)}; this form allows to specify + * a Lookup that provides access to user or context-dependent services. + * + * @param document the document to get Source for + * @param lkp the context for the source + * @return the Source instance + * @see #create(javax.swing.text.Document) + * @since 9.2 + */ + public static Source create ( + Document document, Lookup lkp + ) { Parameters.notNull("document", document); //NOI18N String mimeType = DocumentUtilities.getMimeType(document); @@ -190,21 +238,21 @@ } if (source == null) { - FileObject fileObject = Util.getFileObject(document); + FileObject fileObject = Utilities.getFileObject(document); if (fileObject != null) { - source = Source._get(mimeType, fileObject); + source = Source._get(mimeType, fileObject, lkp); } else { if ("text/x-dialog-binding".equals(mimeType)) { //NOI18N InputAttributes attributes = (InputAttributes) document.getProperty(InputAttributes.class); LanguagePath path = LanguagePath.get(MimeLookup.getLookup(mimeType).lookup(Language.class)); Document doc = (Document) attributes.getValue(path, "dialogBinding.document"); //NOI18N if (doc != null) { - fileObject = Util.getFileObject(doc); + fileObject = Utilities.getFileObject(doc); } else { fileObject = (FileObject) attributes.getValue(path, "dialogBinding.fileObject"); //NOI18N } } - source = new Source(mimeType, document, fileObject); + source = new Source(mimeType, document, fileObject, lkp); } document.putProperty(Source.class, new WeakReference(source)); } @@ -254,31 +302,13 @@ } } if (document != null) return document; - EditorCookie ec = null; - + assert fileObject != null; try { - DataObject dataObject = DataObject.find (fileObject); - ec = dataObject.getLookup ().lookup (EditorCookie.class); - } catch (DataObjectNotFoundException ex) { - //DataobjectNotFoundException may happen in case of deleting opened file - //handled by returning null + return sourceEnv.readDocument(fileObject, forceOpen); + } catch (IOException ioe) { + LOG.log (Level.WARNING, null, ioe); + return null; } - - if (ec == null) return null; - Document doc = ec.getDocument (); - if (doc == null && forceOpen) { - try { - try { - doc = ec.openDocument (); - } catch (UserQuestionException uqe) { - uqe.confirmed (); - doc = ec.openDocument (); - } - } catch (IOException ioe) { - LOG.log (Level.WARNING, null, ioe); - } - } - return doc; } /** @@ -329,7 +359,7 @@ // but they usually don't and this should be good enough for Snapshots. try { if (fileObject.isValid ()) { - if (fileObject.getSize() <= Installer.MAX_FILE_SIZE) { + if (fileObject.getSize() <= Utilities.getMaxFileSize()) { final InputStream is = fileObject.getInputStream (); assert is != null : "FileObject.getInputStream() returned null for FileObject: " + FileUtil.getFileDisplayName(fileObject); //NOI18N try { @@ -396,7 +426,7 @@ new Object[]{ FileUtil.getFileDisplayName(fileObject), fileObject.getSize(), - Installer.MAX_FILE_SIZE + Utilities.getMaxFileSize() }); } } @@ -480,6 +510,7 @@ SourceAccessor.setINSTANCE(new MySourceAccessor()); } + private final Lookup context; private final String mimeType; private final FileObject fileObject; private final Document document; @@ -493,25 +524,43 @@ private Map, SchedulerEvent> schedulerEvents; //GuardedBy(this) private SourceCache cache; - //GuardedBy(this) + //GuardedBy(flags) private volatile long eventId; - //Changes handling - private final EventSupport support = new EventSupport (this); + private volatile boolean listeningOnChanges; + /** + * SourceEnvironment callback + */ + private final SourceControl ctrl = new SourceControl(this); + /** + * Binding of source to its environment + */ + private final SourceEnvironment sourceEnv; + /** + * Demultiplexes events from all parsers to SourceEnvironment. + */ + private final ParserEventForward peFwd; + + @SuppressWarnings("LeakingThisInConstructor") private Source ( String mimeType, Document document, - FileObject fileObject + FileObject fileObject, + Lookup context ) { this.mimeType = mimeType; this.document = document; this.fileObject = fileObject; + this.context = context; + this.peFwd = new ParserEventForward(); + this.sourceEnv = Utilities.createEnvironment(this, ctrl); } - + @NonNull private static Source _get( @NonNull final String mimeType, - @NonNull final FileObject fileObject) { + @NonNull final FileObject fileObject, + @NonNull final Lookup context) { assert mimeType != null; assert fileObject != null; @@ -519,30 +568,76 @@ final Reference sourceRef = instances.get(fileObject); Source source = sourceRef == null ? null : sourceRef.get(); if (source == null || !mimeType.equals(source.getMimeType())) { - source = new Source(mimeType, null, fileObject); + source = new Source(mimeType, null, fileObject, context); instances.put(fileObject, new WeakReference<>(source)); } + assert source.context == context; return source; } } private void assignListeners () { - boolean listen = !suppressListening.get(); + final boolean listen = !suppressListening.get(); if (listen) { - support.init(); + if (listeningOnChanges) { + return; + } + synchronized (TaskProcessor.INTERNAL_LOCK) { + if (listeningOnChanges) { + return; + } + try { + sourceEnv.activate(); + } finally { + listeningOnChanges = true; + } + } } } + private void setFlags(final Set flags) { + // synchronized because of eventId + synchronized (flags) { + this.flags.addAll(flags); + eventId++; + } + } + + private void setSourceModification (boolean sourceChanged, int startOffset, int endOffset) { + ASourceModificationEvent oldSourceModificationEvent; + ASourceModificationEvent newSourceModificationEvent; + do { + oldSourceModificationEvent = sourceModificationEvent.get(); + boolean mergedChange = sourceChanged | (oldSourceModificationEvent == null ? false : oldSourceModificationEvent.sourceChanged()); + newSourceModificationEvent = new ASourceModificationEvent (this, mergedChange, startOffset, endOffset); + } while (!sourceModificationEvent.compareAndSet(oldSourceModificationEvent, newSourceModificationEvent)); + } + + private void mimeTypeMayChanged() { + final FileObject file = getFileObject(); + if (file != null && !Objects.equals(getMimeType(), file.getMIMEType())) { + synchronized (Source.class) { + instances.remove(file); + } + } + } + + private SourceCache getCache() { + synchronized (TaskProcessor.INTERNAL_LOCK) { + if (cache == null) + cache = new SourceCache (this, null); + return cache; + } + } + + @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject") // accessing internals is accessor's job private static class MySourceAccessor extends SourceAccessor { @Override public void setFlags (final Source source, final Set flags) { assert source != null; assert flags != null; - synchronized (source.flags) { - source.flags.addAll(flags); - source.eventId++; - } + source.setFlags(flags); } @Override @@ -555,7 +650,7 @@ @Override public boolean cleanFlag (final Source source, final SourceFlags flag) { assert source != null; - assert flag != null; + assert flag != null; return source.flags.remove(flag); } @@ -585,6 +680,11 @@ } @Override + public void revalidate(Source source, int delay) { + source.ctrl.revalidate(delay); + } + + @Override public boolean invalidate(Source source, long id, Snapshot snapshot) { assert source != null; synchronized (TaskProcessor.INTERNAL_LOCK) { @@ -594,9 +694,7 @@ else { //The eventId and flags are bound long eventId; - synchronized (source.flags) { - eventId = source.eventId; - } + eventId = source.eventId; if (id != eventId) { return false; } @@ -610,6 +708,11 @@ } } } + + @Override + public void attachScheduler(Source src, SchedulerControl sched, boolean attach) { + src.sourceEnv.attachScheduler(sched, attach); + } @Override public Parser getParser(Source source) { @@ -636,9 +739,15 @@ } @Override - public EventSupport getEventSupport (final Source source) { + public SourceControl getEnvControl(final Source source) { assert source != null; - return source.support; + return source.ctrl; + } + + @Override + public SourceEnvironment getEnv(final Source source) { + assert source != null; + return source.sourceEnv; } @Override @@ -650,24 +759,13 @@ @Override public void setSourceModification (Source source, boolean sourceChanged, int startOffset, int endOffset) { assert source != null; - ASourceModificationEvent oldSourceModificationEvent; - ASourceModificationEvent newSourceModificationEvent; - do { - oldSourceModificationEvent = source.sourceModificationEvent.get(); - boolean mergedChange = sourceChanged | (oldSourceModificationEvent == null ? false : oldSourceModificationEvent.sourceChanged()); - newSourceModificationEvent = new ASourceModificationEvent (source, mergedChange, startOffset, endOffset); - } while (!source.sourceModificationEvent.compareAndSet(oldSourceModificationEvent, newSourceModificationEvent)); + source.setSourceModification(sourceChanged, startOffset, endOffset); } @Override public void mimeTypeMayChanged(@NonNull final Source source) { assert source != null; - final FileObject file = source.getFileObject(); - if (file != null && !Objects.equals(source.getMimeType(), file.getMIMEType())) { - synchronized (Source.class) { - instances.remove(file); - } - } + source.mimeTypeMayChanged(); } @Override @@ -735,11 +833,7 @@ @Override public SourceCache getCache (final Source source) { assert source != null; - synchronized (TaskProcessor.INTERNAL_LOCK) { - if (source.cache == null) - source.cache = new SourceCache (source, null); - return source.cache; - } + return source.getCache(); } @Override @@ -806,9 +900,14 @@ return origCache; } + @Override + @NonNull + public ParserEventForward getParserEventForward(@NonNull final Source source) { + return source.peFwd; + } } // End of MySourceAccessor class - static class ASourceModificationEvent extends SourceModificationEvent { + private static class ASourceModificationEvent extends SourceModificationEvent { private int startOffset; private int endOffset; @@ -829,7 +928,17 @@ int _endOffset ) { startOffset = Math.min (startOffset, _startOffset); - endOffset = Math.min (endOffset, _endOffset); + endOffset = Math.max (endOffset, _endOffset); + } + + @Override + public int getAffectedStartOffset() { + return startOffset; + } + + @Override + public int getAffectedEndOffset() { + return endOffset; } @Override @@ -838,4 +947,17 @@ return "SourceModificationEvent " + startOffset + ":" + endOffset; } } + + /** + * Returns a Lookup providing a context for the source. + * In the presence of multiple scopes or users in the system, this Lookup may + * provide access to necessary context-dependent services or scope-dependent data. + *

+ * A Source may be created with a Lookup, or it will use the default Lookup as a context + * @return Lookup. + * @since 9.2 + */ + public Lookup getLookup() { + return context; + } } --- a/parsing.api/src/org/netbeans/modules/parsing/impl/IndexerBridge.java +++ a/parsing.api/src/org/netbeans/modules/parsing/impl/IndexerBridge.java @@ -0,0 +1,67 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.parsing.impl; + +/** + * This interface is a pseudoAPI between Parsing and Indexing support. + * The Parsing support needs to communicate with Indexing and coordinate task planning. + * Indexing API can use impl dependency on Parsing API to call the necessary services, + * but Parsing API must use a bridge implemented by Indexing to call back. + *

+ * This pseudoAPI can be changed at any time without any warning! + * + * @author sdedic + * @since 9.2 + */ +public interface IndexerBridge { + /** + * @return True if the indexer is in any other state than idle. + */ + public boolean isIndexing(); + + /** + * + * @return true, if the calling thread executes in the indexer's protected mode. + */ + public boolean ownsProtectedMode(); +} --- a/parsing.api/src/org/netbeans/modules/parsing/implspi/EnvironmentFactory.java +++ a/parsing.api/src/org/netbeans/modules/parsing/implspi/EnvironmentFactory.java @@ -0,0 +1,113 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.parsing.implspi; + +import java.util.Collection; +import java.util.concurrent.Callable; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.spi.Parser; +import org.netbeans.modules.parsing.spi.Scheduler; +import org.openide.util.Lookup; + +/** + * Factory for {@link SourceEnvironment} instances. The Factory method is given + * a {@link Source.EnvControl} object so the created SourceEnvironment may + * manipulate the Source state using privileged APIs. + *

+ * An instance of this Provider must be registered in the default Lookup. The first + * instance found will be used. + * @since 9.2 + */ +public interface EnvironmentFactory { + /** + * Provides access to context-dependent Lookup, to multi-user environment + * @return context-depedent Lookup + */ + public Lookup getContextLookup(); + + /** + * Parovides a class of a predefined Scheduler. + * The predefined Schedulers are available as final field values. + * + * @param schedulerName + * @return + */ + @CheckForNull + public Class findStandardScheduler(@NonNull String schedulerName); + + /** + * Creates parser for the mime type + * @param mimeType + * @return + */ + public Parser findMimeParser(Lookup context, String mimeType); + + public Collection getSchedulers(Lookup context); + + /** + * Creates an environment for the specified Source. + * The passed control object may be {@code null}, which means the source cannot + * be controlled by environment and listening may not be necessary. + * + * @param src the Source instance + * @param control the control object; if null, the source cannot be controlled. + * @return the SourceEnvironment, must not be null. + */ + @NonNull + public SourceEnvironment createEnvironment(@NonNull Source src, @NullAllowed SourceControl control); + + + /** + * Runs a priority I/O operation. The environment may lock out or suspend + * some (background) activities during the priority I/O operation. + * + * @param type of result + * @param r the code to execute + * @return computed result + * @throws Exception propagated from the executed code + */ + public abstract T runPriorityIO (final Callable r) throws Exception; +} --- a/parsing.api/src/org/netbeans/modules/parsing/implspi/ProfilerSupport.java +++ a/parsing.api/src/org/netbeans/modules/parsing/implspi/ProfilerSupport.java @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. +*/ + +package org.netbeans.modules.parsing.implspi; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Semi-private SPI for self-profiling of the parsing system. + * + * @author sdedic + * @since 9.2 + */ +public abstract class ProfilerSupport { + /** + * Starts the profiling. The support will collect data until {@link #cancel} or {@link #stopAndSnapshot} + * is called. + */ + public abstract void start(); + + /** + * Cancels the profiling and discards data already collected. + */ + public abstract void cancel(); + + /** + * Stops the profiling and flushes the data. + * + * @param dos output stream to store the collected data. + */ + public abstract void stopAndSnapshot(DataOutputStream dos) throws IOException; + + public interface Factory { + public ProfilerSupport create(String id); + } +} --- a/parsing.api/src/org/netbeans/modules/parsing/implspi/SchedulerControl.java +++ a/parsing.api/src/org/netbeans/modules/parsing/implspi/SchedulerControl.java @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.parsing.implspi; + +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.spi.Scheduler; + +/** + * Allows the environment implementation to reschedule tasks for a + * source. The instance is passed to {@link SourceEnvironment#attachScheduler}. + * @author sdedic + * @since 9.2 + */ +public interface SchedulerControl { + /** + * Provides access to the controlled Scheduler + * @return the scheduler instance + */ + public Scheduler getScheduler(); + + /** + * Notifies about Source change. Should be called when the scheduler + * should reschedule source's tasks or when the Source itself changes, i.e. + * file is moved/renamed etc. + * + * @param newSource the current Source for the Scheduler + */ + public void sourceChanged(Source newSource); +} --- a/parsing.api/src/org/netbeans/modules/parsing/implspi/SourceControl.java +++ a/parsing.api/src/org/netbeans/modules/parsing/implspi/SourceControl.java @@ -0,0 +1,172 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.parsing.implspi; + +import java.util.EnumSet; +import java.util.Set; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.impl.SourceAccessor; +import org.netbeans.modules.parsing.impl.SourceFlags; +import org.netbeans.modules.parsing.impl.TaskProcessor; +import org.openide.util.Parameters; +import org.openide.util.RequestProcessor; + +/** + * Allows to control Source state within the Parsing subsystem based on + * external events. + * The {@link SourceEnvironment} gets this instance to forward interesting + * events to the parser. It is possible to invalidate the source, region of source. + * @author sdedic + * @since 9.2 + */ +public final class SourceControl { + + private static final RequestProcessor RP = new RequestProcessor ( + "parsing-event-collector", //NOI18N + 1, + false, + false); + + private final RequestProcessor.Task resetTask; + + private final Source source; + + /** + * Creates a new control object for the source. + * + * @param source the source which is controlled + */ + public SourceControl(@NonNull final Source source) { + Parameters.notNull("source", source); //NOI18N + this.source = source; + this.resetTask = RP.create(new Runnable() { + @Override + public void run() { + if (SourceAccessor.getINSTANCE().getEnv(source).isReparseBlocked()) { + return; + } + TaskProcessor.resetStateImpl(source); + } + }); + } + + + /** + * Provides reference to the Source. The reference is provided for convenience + * to help proper garbage collection of the Source object. If the client keeps + * a Source instance, it should use WeakReference to store it. + * @return Source instance or {@code null} + */ + @NonNull + public Source getSource() { + return source; + } + + /** + * Informs that the source was changed in an unspecified way, and possibly + * its mime was changed. + * The source will be reparsed and if {@code mimeChanged} is true, the MIME + * type will be re-read and the appropriate parser will be used for parsing. + *

+ * The {@code mimeChanged} flag is used for optimization; set it aggressively, + * so proper parser is looked up. + * + * @param mimeChanged true, if mime type might have changed. + */ + public void sourceChanged(final boolean mimeChanged) { + final SourceAccessor sa = SourceAccessor.getINSTANCE(); + final Set flags = EnumSet.of( + SourceFlags.CHANGE_EXPECTED, + SourceFlags.INVALID, + SourceFlags.RESCHEDULE_FINISHED_TASKS); + if (mimeChanged) { + sa.mimeTypeMayChanged(source); + } + sa.setSourceModification(source, true, -1, -1); + sa.setFlags(source, flags); + TaskProcessor.resetState(source, true, true); + } + + /** + * Informs that part of the source was edited. The parser implementation + * may reparse just a portion of the text or a whole (depends on the + * implementation). Setting {@code startOffset} or {@code endOffset} + * to -1 will execute an equivalent of {@code sourceChanged(false)}. + * + * @param startOffset start of the change + * @param endOffset end of the change + */ + public void regionChanged(int startOffset, int endOffset) { + final SourceAccessor sa = SourceAccessor.getINSTANCE(); + final Set flags = EnumSet.of( + SourceFlags.CHANGE_EXPECTED, + SourceFlags.INVALID, + SourceFlags.RESCHEDULE_FINISHED_TASKS); + sa.setSourceModification(source, true, startOffset, endOffset); + sa.setFlags(source, flags); + TaskProcessor.resetState(source, true, true); + } + + /** + * Informs about a non-text change in the Source, such as caret movement + * or focus. Does not invalidate the parsing result, but may re-execute certain + * tasks. + */ + public void stateChanged() { + final SourceAccessor sa = SourceAccessor.getINSTANCE(); + final Set flags = EnumSet.of(SourceFlags.CHANGE_EXPECTED); + sa.setSourceModification(source, false, -1, -1); + sa.setFlags(source, flags); + TaskProcessor.resetState(source, false, true); + } + + /** + * Marks the source for reparsing after the specified delay. + * + * @param delay time in milliseconds. + */ + public void revalidate(int delay) { + resetTask.schedule(delay); + } +} --- a/parsing.api/src/org/netbeans/modules/parsing/implspi/SourceEnvironment.java +++ a/parsing.api/src/org/netbeans/modules/parsing/implspi/SourceEnvironment.java @@ -0,0 +1,166 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. + */ +package org.netbeans.modules.parsing.implspi; + +import java.io.IOException; +import javax.swing.event.ChangeListener; +import javax.swing.text.Document; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.impl.ParserEventForward; +import org.netbeans.modules.parsing.impl.SourceAccessor; +import org.netbeans.modules.parsing.impl.event.FileChangeSupport; +import org.netbeans.modules.parsing.impl.event.ParserChangeSupport; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Parameters; + +/** + * Connects the Source instance to its environment.It is not implemented by + * Parsing API as it may introduce extra dependencies on file or object access + * libraries (e.g. Data Systems API). In exchange, the Provider gets a control + * interface, which can be used to signal source change events originating from + * the storage/file/DataObject. + *

+ * Instance of the SourceEnvironment is referenced from the Source object. It is + * advised not to store Source reference, but rather extract the Source instance + * from the {@link Source.EnvControl} to allow Source instances to be GCed. + * + * @author sdedic + * @since 9.2 + */ +public abstract class SourceEnvironment { + + private final SourceControl sourceControl; + private FileChangeListener fileChangeListener; + private ChangeListener parserListener; + + /** + * Initialized a new SourceEnvironment. + * @param sourceControl handle to control a Source object + */ + protected SourceEnvironment(@NonNull final SourceControl sourceControl) { + Parameters.notNull("sourceControl", sourceControl); //NOI18N + this.sourceControl = sourceControl; + } + /** + * Reads the Document based on the Source's properties + * + * @param f the FIleObject + * @param forceOpen true, if the document should be opened even though + * it does not exist yet + * @return the document, or {@code null} if the document was not opened. + */ + @CheckForNull + public abstract Document readDocument(@NonNull FileObject f, boolean forceOpen) throws IOException; + + /** + * Attaches a Scheduler to the source in this environment. The implementation + * may need to listen on certain environment objects in order to schedule + * tasks at appropriate time. + *

+ * + * @param s the scheduler to attach or detach + * @param attach if false, the scheduler is just detached from the source. + */ + public abstract void attachScheduler(@NonNull SchedulerControl s, boolean attach); + + /** + * Notifies that the source was actually used, and the parser wants to be + * notified on changes through {@link SourceControl}. + * Until this method is called, the environment implementation need not to + * listen or forward source affecting events to the parser API. + */ + public abstract void activate(); + + /** + * Returns true, if reparse should NOT occur immediately when source is invalidated. + * This is used when e.g. completion is active; postpones reparsing. The SourceEnvironment + * is responsible for calling {@link SourceControl#revalidate()} when the condition changes. + * + * @return true, if a reparse should not be scheduled. + */ + public abstract boolean isReparseBlocked(); + + /** + * Returns the {@link SourceControl}. + * @return the {@link SourceControl} + */ + protected final SourceControl getSourceControl() { + return this.sourceControl; + } + + /** + * Starts listening on file changes. + * Should be called from {@link SourceEnvironment#activate} + */ + protected final void listenOnFileChanges() { + final FileObject fo = sourceControl.getSource().getFileObject(); + if (fo != null) { + fileChangeListener = new FileChangeSupport(sourceControl); + fo.addFileChangeListener(FileUtil.weakFileChangeListener(this.fileChangeListener,fo)); + } + } + + /** + * Starts listening on {@link Source} parsers. + * Should be called from {@link SourceEnvironment#activate} + */ + protected final void listenOnParser() { + final ParserEventForward peFwd = SourceAccessor.getINSTANCE().getParserEventForward( + sourceControl.getSource()); + parserListener = new ParserChangeSupport(sourceControl); + peFwd.addChangeListener(parserListener); + } + + /** + * Returns a {@link SourceEnvironment} for given {@link Source}. + * @param source the {@link Source} to return {@link SourceEnvironment} for + * @return the {@link SourceEnvironment} + */ + @NonNull + protected static SourceEnvironment forSource(@NonNull final Source source) { + return SourceAccessor.getINSTANCE().getEnv(source); + } +} --- a/parsing.api/src/org/netbeans/modules/parsing/implspi/TaskProcessorControl.java +++ a/parsing.api/src/org/netbeans/modules/parsing/implspi/TaskProcessorControl.java @@ -0,0 +1,83 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2014 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.parsing.implspi; + +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.impl.SourceAccessor; +import org.netbeans.modules.parsing.impl.TaskProcessor; +import org.openide.util.Parameters; + +/** + * Allows to control the parsing susbsytem operation. + * @author sdedic + * @since 9.2 + */ +public final class TaskProcessorControl { + /** + * Initialize the parsing and scheduling system. The method should be called + * at "appropriate time", for example when the UI starts and is ready to accept + * user input. + */ + public static void initialize() { + SourceAccessor.getINSTANCE().init(); + } + + /** + * Suspends {@link SchedulerTask}s execution. + * Cancels currently running {@link SchedulerTask} and do + * not schedule any ready {@link SchedulerTask}. + */ + public static void suspendSchedulerTasks(@NonNull final Source source) { + Parameters.notNull("source", source); //NOI18N + TaskProcessor.resetState(source, true, true); + } + + /** + * Resumes {@link SchedulerTask}s execution. + * Schedules ready {@link SchedulerTask}s. + */ + public static void resumeSchedulerTasks() { + TaskProcessor.resetStateImpl(null); + } +} --- a/parsing.api/src/org/netbeans/modules/parsing/implspi/package.html +++ a/parsing.api/src/org/netbeans/modules/parsing/implspi/package.html @@ -0,0 +1,49 @@ + + + + + This package contains SPI and callbacks to bind Parser support to the environment + - FileObjects, DataObjects or windowing system. Only a single implementation of + the EnvironmentFactory is expected. + + --- a/parsing.api/src/org/netbeans/modules/parsing/spi/EmbeddingProvider.java +++ a/parsing.api/src/org/netbeans/modules/parsing/spi/EmbeddingProvider.java @@ -49,7 +49,6 @@ import java.util.List; import org.netbeans.modules.parsing.api.Embedding; import org.netbeans.modules.parsing.api.Snapshot; -import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; /** @@ -93,7 +92,7 @@ * Registration of the {@link EmbeddingProvider}. * Creates a mime lookup registration of the {@link TaskFactory} for * annotated {@link EmbeddingProvider}. It also provides the target - * mime type of the created embedding which allows the {@link QuerySupport} + * mime type of the created embedding which allows the indexing QuerySupport * to correctly mark dirty embedded indexers. * * @since 1.57 --- a/parsing.api/src/org/netbeans/modules/parsing/spi/Scheduler.java +++ a/parsing.api/src/org/netbeans/modules/parsing/spi/Scheduler.java @@ -42,25 +42,17 @@ package org.netbeans.modules.parsing.spi; -import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.modules.parsing.api.Source; -import org.netbeans.modules.parsing.impl.CurrentDocumentScheduler; -import org.netbeans.modules.parsing.impl.CursorSensitiveScheduler; import org.netbeans.modules.parsing.impl.SchedulerAccessor; -import org.netbeans.modules.parsing.impl.SelectedNodesScheduler; import org.netbeans.modules.parsing.impl.SourceAccessor; import org.netbeans.modules.parsing.impl.SourceCache; -import org.openide.filesystems.FileObject; -import org.openide.loaders.DataObject; -import org.openide.loaders.DataObjectNotFoundException; - +import org.netbeans.modules.parsing.impl.Utilities; +import org.netbeans.modules.parsing.implspi.SchedulerControl; import org.openide.util.RequestProcessor; import org.openide.util.RequestProcessor.Task; -import org.openide.util.WeakListeners; /** @@ -85,23 +77,6 @@ private static final Logger LOG = Logger.getLogger(Scheduler.class.getName()); - private final PropertyChangeListener listener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (DataObject.PROP_PRIMARY_FILE.equals(evt.getPropertyName())) { - final DataObject dobj = (DataObject) evt.getSource(); - final Source newSource = Source.create(dobj.getPrimaryFile()); - if (newSource != null) { - LOG.log( - Level.FINE, - "Rescheduling {0} due to change of primary file.", //NOI18N - dobj.getPrimaryFile()); - schedule(newSource, new SchedulerEvent(newSource)); - } - } - } - }; - /** * May be changed by unit test */ @@ -121,7 +96,7 @@ * */ public static final Class - CURSOR_SENSITIVE_TASK_SCHEDULER = CursorSensitiveScheduler.class; + CURSOR_SENSITIVE_TASK_SCHEDULER; /** * This implementations of {@link Scheduler} reschedules all tasks when: @@ -131,15 +106,21 @@ * */ public static final Class - EDITOR_SENSITIVE_TASK_SCHEDULER = CurrentDocumentScheduler.class; + EDITOR_SENSITIVE_TASK_SCHEDULER; /** * This implementations of {@link Scheduler} reschedules all tasks when * nodes selected in editor are changed. */ public static final Class - SELECTED_NODES_SENSITIVE_TASK_SCHEDULER = SelectedNodesScheduler.class; + SELECTED_NODES_SENSITIVE_TASK_SCHEDULER; + static { + CURSOR_SENSITIVE_TASK_SCHEDULER = Utilities.findDefaultScheduler("CURSOR_SENSITIVE_TASK_SCHEDULER"); + EDITOR_SENSITIVE_TASK_SCHEDULER = Utilities.findDefaultScheduler("EDITOR_SENSITIVE_TASK_SCHEDULER"); + SELECTED_NODES_SENSITIVE_TASK_SCHEDULER = Utilities.findDefaultScheduler("SELECTED_NODES_SENSITIVE_TASK_SCHEDULER"); + } + /** * Reschedule all tasks registered for this Scheduler (see * {@link ParserResultTask#getSchedulerClass()}. @@ -155,6 +136,8 @@ requestProcessor; private Task task; + private final SchedulerControl ctrl = new Control(this); + /** * Reschedule all tasks registered for this Scheduler (see * {@link ParserResultTask#getSchedulerClass()}, and sets new {@link Source}s for them. @@ -177,45 +160,29 @@ false, false); } - if (this.source != source) { + boolean different = this.source != source; + + if (different) { if (this.source != null) { final SourceCache cache = SourceAccessor.getINSTANCE().getCache(this.source); cache.unscheduleTasks(Scheduler.this.getClass()); - if (wlistener != null) { - final FileObject fo = this.source.getFileObject(); - if (fo != null) { - try { - final DataObject dobj = DataObject.find(fo); - dobj.removePropertyChangeListener(wlistener); - } catch (DataObjectNotFoundException nfe) { - //No DataObject for file - ignore - } - } - } + SourceAccessor.getINSTANCE().attachScheduler(this.source, ctrl, false); } this.source = source; - if (source != null) { - final FileObject fo = source.getFileObject(); - if (fo != null) { - try { - final DataObject dobj = DataObject.find(fo); - wlistener = WeakListeners.propertyChange(listener, dobj); - dobj.addPropertyChangeListener(wlistener); - } catch (DataObjectNotFoundException ex) { - //No DataObject for file - ignore - } - } - } } if (source == null) { return; } + if (different) { + SourceAccessor.getINSTANCE().attachScheduler(source, ctrl, true); + } task = requestProcessor.create (new Runnable () { @Override public void run () { SourceCache cache = SourceAccessor.getINSTANCE ().getCache (source); SourceAccessor.getINSTANCE ().setSchedulerEvent (source, Scheduler.this, event); //S ystem.out.println ("\nSchedule tasks (" + Scheduler.this + "):"); + LOG.fine("Scheduling tasks for :" + source + " and scheduler " + this); cache.scheduleTasks (Scheduler.this.getClass ()); } }); @@ -247,10 +214,35 @@ return scheduler.createSchedulerEvent (event); } } + + /** + * Allows to control the scheduler and schedule tasks. An instance of Control + * is passed to the {@link SourceEnvironment#attachScheduler} method when the + * Source is bound to a Scheduler. + *

+ * The client is NOT expected to create instances of this class; + */ + private static final class Control implements SchedulerControl { + private final Scheduler scheduler; + + /** + * + * @param scheduler + */ + public Control(final Scheduler scheduler) { + super(); + this.scheduler = scheduler; + } + + public Scheduler getScheduler() { + return scheduler; + } + + @Override + public void sourceChanged(Source newSource) { + scheduler.schedule(newSource, new SchedulerEvent(newSource)); + } + } + } - - - - - --- a/parsing.api/src/org/netbeans/modules/parsing/spi/SourceModificationEvent.java +++ a/parsing.api/src/org/netbeans/modules/parsing/spi/SourceModificationEvent.java @@ -52,7 +52,6 @@ public class SourceModificationEvent extends EventObject { private final boolean sourceChanged; - /** * Creates a new {@link SourceModificationEvent} @@ -92,12 +91,27 @@ public boolean sourceChanged() { return sourceChanged; } + + /** + * Returns start offset of the change that affected the source. + * @return offset or -1 if the source was not affected + * @since 9.1 + */ + public int getAffectedStartOffset() { + return -1; + } + /** + * Returns end offset of the change that affected the source. + * @return offset or -1 if the source was not affected + * @since 9.1 + */ + public int getAffectedEndOffset() { + return -1; + } + @Override public String toString () { return "SourceModificationEvent " + hashCode () + "(source: " + source + ")"; } }