001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.napi.gsfret.source;
042:
043: import java.io.IOException;
044: import java.util.ArrayList;
045: import java.util.Collection;
046: import java.util.HashMap;
047: import java.util.List;
048: import java.util.Map;
049: import java.util.Map.Entry;
050: import java.util.logging.Level;
051: import java.util.logging.Logger;
052: import org.netbeans.modules.gsf.api.CancellableTask;
053: import org.netbeans.napi.gsfret.source.Source.Priority;
054: import org.openide.filesystems.FileObject;
055: import org.netbeans.napi.gsfret.source.support.EditorAwareSourceTaskFactory;
056: import org.netbeans.napi.gsfret.source.support.CaretAwareSourceTaskFactory;
057: import org.netbeans.napi.gsfret.source.support.LookupBasedSourceTaskFactory;
058: import org.netbeans.modules.gsfret.source.parsing.FileObjects;
059: import org.openide.ErrorManager;
060: import org.openide.util.RequestProcessor;
061:
062: /**
063: * This class is based on JavaSourceTaskFactory in Retouche
064: * This file is originally from Retouche, the Java Support
065: * infrastructure in NetBeans. I have modified the file as little
066: * as possible to make merging Retouche fixes back as simple as
067: * possible.
068: *
069: *
070: * XXX: SPI?
071: *
072: * A factory for tasks that will be run in the {@link Source} Java parsing harness.
073: *
074: * Please note that there is usually no need to implement this class directly,
075: * as there are support classes for common {@link SourceTaskFactory} implementations.
076: *
077: * This factory should be registered in the global lookup by listing its fully qualified
078: * name in file <code>META-INF/services/org.netbeans.modules.gsfpath.api.source.SourceTaskFactory</code>.
079: *
080: * @see EditorAwareSourceTaskFactory
081: * @see CaretAwareSourceTaskFactory
082: * @see LookupBasedSourceTaskFactory
083: *
084: * @author Jan Lahoda
085: */
086: public abstract class SourceTaskFactory {
087:
088: private static final Logger LOG = Logger
089: .getLogger(SourceTaskFactory.class.getName());
090: static final String BEFORE_ADDING_REMOVING_TASKS = "beforeAddingRemovingTasks"; //NOI18N
091: static final String FILEOBJECTS_COMPUTATION = "fileObjectsComputation"; //NOI18N
092:
093: private final Phase phase;
094: private final Priority priority;
095:
096: /**Construct the SourceTaskFactory with given {@link Phase} and {@link Priority}.
097: *
098: * @param phase phase to use for tasks created by {@link #createTask}
099: * @param priority priority to use for tasks created by {@link #createTask}
100: */
101: protected SourceTaskFactory(Phase phase, Priority priority) {
102: this .phase = phase;
103: this .priority = priority;
104: this .file2Task = new HashMap<FileObject, CancellableTask<CompilationInfo>>();
105: this .file2JS = new HashMap<FileObject, Source>();
106: }
107:
108: /**Create task for a given file. This task will be registered into the {@link Source}
109: * parsing harness with a given {@link #getPriority priority} and {@link #getPhase phase}.
110: *
111: * Please note that this method should run as quickly as possible.
112: *
113: * @param file for which file the task should be created.
114: * @return created {@link CancellableTask} for a given file.
115: */
116: protected abstract CancellableTask<CompilationInfo> createTask(
117: FileObject file);
118:
119: /**Specifies on which files should be registered tasks created by this factory.
120: * On {@link Source}'s corresponding to {@link FileObject}s returned from
121: * this method will be registered tasks created by the {@link #createTask} method
122: * of this factory.
123: *
124: * If this list changes, a change event should be fired to all registered
125: * {@link ChangeListener}s.
126: *
127: * @return list of {@link FileObject} on which tasks from this factory should be
128: * registered.
129: * @see #createTask
130: * @see #addChangeListener
131: * @see EditorAwareSourceTaskFactory
132: * @see CaretAwareSourceTaskFactory
133: */
134: protected abstract Collection<FileObject> getFileObjects();
135:
136: /**Notify the infrastructure that the collection of fileobjects has been changed.
137: * The infrastructure calls {@link #getFileObjects()} to get a new collection files.
138: */
139: protected final void fileObjectsChanged() {
140: LOG.log(Level.FINEST, FILEOBJECTS_COMPUTATION);
141:
142: final List<FileObject> currentFiles = new ArrayList(
143: getFileObjects());
144:
145: if (SYNCHRONOUS_EVENTS) {
146: stateChangedImpl(currentFiles);
147: } else {
148: WORKER.post(new Runnable() {
149: public void run() {
150: stateChangedImpl(currentFiles);
151: }
152: });
153: }
154: }
155:
156: /**for tests:
157: */
158: static boolean SYNCHRONOUS_EVENTS = false;
159:
160: private void stateChangedImpl(List<FileObject> currentFiles) {
161: Map<Source, CancellableTask<CompilationInfo>> toRemove = new HashMap<Source, CancellableTask<CompilationInfo>>();
162: Map<Source, CancellableTask<CompilationInfo>> toAdd = new HashMap<Source, CancellableTask<CompilationInfo>>();
163:
164: synchronized (this ) {
165: List<FileObject> addedFiles = new ArrayList(currentFiles);
166: List<FileObject> removedFiles = new ArrayList(file2Task
167: .keySet());
168:
169: addedFiles.removeAll(file2Task.keySet());
170: removedFiles.removeAll(currentFiles);
171:
172: //remove old tasks:
173: for (FileObject r : removedFiles) {
174: Source source = file2JS.remove(r);
175:
176: if (source == null) {
177: //TODO: log
178: continue;
179: }
180:
181: toRemove.put(source, file2Task.remove(r));
182: }
183:
184: //add new tasks:
185: for (FileObject a : addedFiles) {
186: if (a == null)
187: continue;
188: if (!a.isValid()) {
189: continue;
190: }
191:
192: Source js = Source.forFileObject(a);
193:
194: if (js != null) {
195: CancellableTask<CompilationInfo> task = createTask(a);
196:
197: toAdd.put(js, task);
198:
199: file2Task.put(a, task);
200: file2JS.put(a, js);
201: }
202: }
203: }
204:
205: LOG.log(Level.FINEST, BEFORE_ADDING_REMOVING_TASKS);
206:
207: for (Entry<Source, CancellableTask<CompilationInfo>> e : toRemove
208: .entrySet()) {
209: ACCESSOR2.removePhaseCompletionTask(e.getKey(), e
210: .getValue());
211: }
212:
213: for (Entry<Source, CancellableTask<CompilationInfo>> e : toAdd
214: .entrySet()) {
215: try {
216: ACCESSOR2.addPhaseCompletionTask(e.getKey(), e
217: .getValue(), phase, priority);
218: } catch (FileObjects.InvalidFileException ie) {
219: LOG
220: .info("Source.addPhaseCompletionTask called on deleted file"); //NOI18N
221: } catch (IOException ex) {
222: ErrorManager.getDefault().notify(ex);
223: }
224: }
225: }
226:
227: /**Re-run task created by this factory for given file.
228: * If the task has not yet been run, does nothing.
229: *
230: * @param file task created by this factory for this file is re-run.
231: */
232: protected final synchronized void reschedule(FileObject file)
233: throws IllegalArgumentException {
234: Source source = file2JS.get(file);
235:
236: if (source == null) {
237: // throw new IllegalArgumentException("No Source for given file.");
238: return;
239: }
240:
241: CancellableTask<CompilationInfo> task = file2Task.get(file);
242:
243: if (task == null) {
244: // throw new IllegalArgumentException("This factory did not created any task for " + FileUtil.getFileDisplayName(file)); // NOI18N
245: return;
246: }
247:
248: ACCESSOR2.rescheduleTask(source, task);
249: }
250:
251: private final Map<FileObject, CancellableTask<CompilationInfo>> file2Task;
252: private final Map<FileObject, Source> file2JS;
253:
254: private static RequestProcessor WORKER = new RequestProcessor(
255: "SourceTaskFactory", 1);
256:
257: static {
258: SourceTaskFactoryManager.ACCESSOR = new SourceTaskFactoryManager.Accessor() {
259: public void fireChangeEvent(SourceTaskFactory f) {
260: f.fileObjectsChanged();
261: }
262: };
263: ACCESSOR2 = new Accessor2() {
264: public void addPhaseCompletionTask(Source js,
265: CancellableTask<CompilationInfo> task, Phase phase,
266: Priority priority) throws IOException {
267: js.addPhaseCompletionTask(task, phase, priority);
268: }
269:
270: public void removePhaseCompletionTask(Source js,
271: CancellableTask<CompilationInfo> task) {
272: js.removePhaseCompletionTask(task);
273: }
274:
275: public void rescheduleTask(Source js,
276: CancellableTask<CompilationInfo> task) {
277: js.rescheduleTask(task);
278: }
279: };
280: }
281:
282: static interface Accessor2 {
283: public abstract void addPhaseCompletionTask(Source js,
284: CancellableTask<CompilationInfo> task, Phase phase,
285: Priority priority) throws IOException;
286:
287: public abstract void removePhaseCompletionTask(Source js,
288: CancellableTask<CompilationInfo> task);
289:
290: public abstract void rescheduleTask(Source js,
291: CancellableTask<CompilationInfo> task);
292: }
293:
294: static Accessor2 ACCESSOR2;
295:
296: }
|