001: package Schmortopf.FileStructure;
002:
003: /**
004: * This is a permanent running updater thread, which
005: * updates the file dependencies required for compiling.
006: *
007: * An instance of this class is held and managed by
008: * the FileStructureDescriptionManager.
009: *
010: * Note: This thread scales its cpu consumption automatically.
011: * Because the update entries are heavy ( fsd plus parsertree )
012: * it must hurry up the more entries there are.
013: * If there are many entries ( more than 10 ), it will consume
014: * almost ALL cpu power.
015: * However, in normal operation, when there are 1,2 or 3 entries,
016: * it will be almost on standby and call sleep(5) in its scanners.
017: *
018: * If there are more than 10 entries, this thread will become
019: * very busy and process the queue, until its below this threshold
020: * value. ( Memory increases fast along with the queue size )
021: *
022: * Note: The thread uses wait/notify mechanism. Normally it waits for
023: * the calculated time. But if many entries are appended to its queue,
024: * it will get notified and come alive fast for not letting
025: * grow the queue too much.
026: *
027: * The thread works on the dependencyUpdaterQueue, which is filled
028: * when files are edited by the user, or moved,copied or removed.
029: * It's triggered by FSD updates.
030: *
031: */
032:
033: import java.util.Vector;
034: import javax.swing.tree.DefaultMutableTreeNode;
035:
036: import Shared.Logging.Log;
037:
038: //
039: // Using the ThreadEngine in this object is NOT allowed.
040: // Reason: There are tasks in the ThreadEngine waiting
041: // for this thread to become idle.
042: //
043: // So if this thread performs tasks in the ThreadEngine,
044: // this will create a deadlock for sure, because the
045: // ThreadEngine "threadqueue" processes its entries
046: // one after the other.
047: //
048:
049: import Schmortopf.FileStructure.Descriptions.FileStructureDescription;
050: import Schmortopf.FileComponents.Model.TokenTreeUserObject;
051:
052: public class FileStructureDependenciesUpdater extends Thread {
053:
054: private static final int ForegroundMode = 0;
055: private static final int BackgroundMode = 1;
056:
057: private int threadMode = BackgroundMode;
058:
059: private boolean doTerminate = false;
060:
061: private FileStructureDescriptionManager fsdManager;
062: private FileStructureDependenciesScanner dependenciesScanner;
063:
064: // Contains DependencyUpdaterQueueEntry objects of files, which need an update of their
065: // filedependencies information.
066: private final Vector dependencyUpdaterQueue = new Vector();
067:
068: // If there are more than BusyThreshold entries in the queue,
069: // this thread processes the queue, until there are less than
070: // this amount of entries. (memory reasons)
071: private final static int BusyThreshold = 10;
072:
073: // Some processes disable this updater for a short time for getting
074: // faster updates for the user.
075: // For example, if a new document is set, the method in the EditorPanel
076: // will disable the updater for letting the editor and filecomponent tree
077: // tasks be processed immediately. After theses tasks are finished,
078: // it will enable this updater again.
079: // While the updater is finished, added files will just remain in the
080: // dependencyUpdaterQueue and processed after it is enabled again.
081: // No data is lost while it is disabled.
082: // Note: If a notify() call is made, the updater will be enabled in any case.
083: private boolean updaterIsEnabled = true;
084:
085: // This flag is set, if time is expensive - Usually before
086: // compiling starts, or before the document is changed.
087: // The thread then shouldnt sleep, but process all pending entries.
088: // This flag will be cleared as soon as the queue is empty.
089: private boolean doProcessAllPendingEntries = false;
090:
091: public FileStructureDependenciesUpdater(
092: final FileStructureDescriptionManager fsdManager) {
093: this .fsdManager = fsdManager;
094: this .dependenciesScanner = new FileStructureDependenciesScanner(
095: this .fsdManager);
096: this .setName("FileStructureDependenciesUpdater [permanent]");
097: } // Constructor
098:
099: /**
100: * The thread loop: synchronized cause of the wait() call.
101: */
102: public synchronized void run() {
103: while (!this .doTerminate) {
104: // Calculate the delay as function of the number of (heavy) entries.
105: long delay = this .calculateDelayTime();
106:
107: // If there has been a request call for processing all entries
108: // without pause, test and clear that flag now, if the queue has
109: // become empty:
110: if (this .doProcessAllPendingEntries) {
111: if (this .dependencyUpdaterQueue.size() == 0) {
112: this .doProcessAllPendingEntries = false; // job has been done
113: }
114: }
115:
116: // Note that the thread can be notified and wake up, when we get
117: // more than BusyThreshold entries into the queue.
118: // The notify call is in the appendEntryToQueue() method.
119: // If the updater is not enabled, we continue waiting and don't
120: // process the queue entries.
121: if (!this .doProcessAllPendingEntries) {
122: while (true) {
123: try {
124: this .wait(delay);
125: } catch (Exception anyEx1235) {
126: }
127: // Leave the loop, if its enabled, stay here otherwise.
128: if ((this .updaterIsEnabled) || (this .doTerminate))
129: break;
130: }
131: }
132:
133: //Log.Info("FSDDependencyUpdater> process entry");
134:
135: if (this .dependencyUpdaterQueue.size() <= BusyThreshold) {
136: // Flag true: Allow the scanner to call sleep(2) from time to time
137: // for being better in the background.
138: this .processFirstEntryInQueue(true);
139: } else {
140: // If we have more than BusyThreshold entries, process until we are below this
141: // number ( memory increases fast along with the queue size )
142: while (this .dependencyUpdaterQueue.size() > BusyThreshold) {
143: // Flag false: Don't allow the scanner to call sleep(2).
144: // It should run without interruption by other threads (of same or lower
145: // priority )
146: this .processFirstEntryInQueue(false);
147: } // while
148: }
149:
150: } // while
151: } // run
152:
153: private void processFirstEntryInQueue(final boolean allowWaitStates) {
154: // Try to get the next fsd for update from the queue:
155:
156: // A little side note:
157: // We do NOT synchronize [ =lock ] the dependencyUpdaterQueue during the
158: // whole processing time of this method.
159: // The only complication, which we get for this speed advantage is, that
160: // at the end, we can't just remove the first element, but have to
161: // test before we remove, if it's still containing the same reference.
162: // If not, there's nothing to do.
163:
164: DependencyUpdaterQueueEntry entryForUpdate = null;
165: synchronized (this .dependencyUpdaterQueue) {
166: if (this .dependencyUpdaterQueue.size() > 0) {
167: entryForUpdate = (DependencyUpdaterQueueEntry) this .dependencyUpdaterQueue
168: .elementAt(0);
169: }
170: } // sync
171: if (entryForUpdate != null) {
172:
173: // If it's old and it's member attributes have been released already,
174: // there is nothing to do - it's outdated anyway:
175: if (entryForUpdate.fsd.fullyQualifiedClassNameBuffer
176: .length() > 0) {
177:
178: //ystem.out.println("FSDU> FileStructureDependenciesUpdater starts.");
179:
180: // Invalidate it, until the scanner has finished its work:
181: entryForUpdate.fsd.referencedProjectFilesAreValid = false;
182: // Set the save flag immediately:
183: this .fsdManager.setDataHasChanged();
184:
185: // Go update this one:
186: // If the parser itself has found errors, we can't scan the
187: // parser tree, but have to compile it always:
188: if (entryForUpdate.fsd.parserOutput.success) {
189: // Scan for dependencies. This will set the fsd's referencedProjectFiles Vector
190: // and associated referencedProjectFiles attributes.
191: // Note, that fsd.belongsToProject must be set at this time.
192: this .dependenciesScanner.scanReferencedObjects(
193: entryForUpdate.fsd,
194: entryForUpdate.compilationRootNode,
195: entryForUpdate.fsd.pathNameBuffer
196: .toString(), this .fsdManager,
197: allowWaitStates);
198: } else {
199: // We have errors on parser level:
200: // Set it invalid, so it's reupdated before the next compiler run:
201: entryForUpdate.fsd.referencedProjectFilesAreValid = false;
202: entryForUpdate.fsd.hasToBeCompiledAlways = true;
203: }
204: // Set the save flag again:
205: this .fsdManager.setDataHasChanged();
206:
207: //ystem.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
208: //ystem.out.println("++++ FSDDependenciesUpdater: Entry processed for " +
209: // entryForUpdate.fsd.fullyQualifiedClassNameBuffer.toString() );
210: //ystem.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
211:
212: } // if fsd still valid
213: /*
214: else
215: {
216: ystem.out.println("---------------------------------------------------------");
217: ystem.out.println("---- FSDDependenciesUpdater: Outdated entry has been skipped.");
218: ystem.out.println("---------------------------------------------------------");
219: }
220: */
221:
222: //
223: // The queue has been processed, so REMOVE the processed element.
224: //
225: // IMPORTANT: Before the compiler is launched, this updater is notified,
226: // and the ThreadEngine task, which starts the compiler will
227: // wait, until the dependencyUpdaterQueue is empty. Therefore
228: // it is important, that we remove the first entry ONLY after
229: // it has been fully processed and the dependency fsd information
230: // has been updated.
231: //
232: // See the comments above about non-synchronization (over the whole method)
233: // of the queue. -> Result is:
234: // We only remove the first queue entry, if it still contains the
235: // same reference, otherwise we do nothing:
236: synchronized (this .dependencyUpdaterQueue) {
237: if (this .dependencyUpdaterQueue.size() > 0) {
238: // Reference check:
239: if (this .dependencyUpdaterQueue.elementAt(0) == entryForUpdate) {
240: this .dependencyUpdaterQueue.removeElementAt(0);
241: }
242: }
243: } // sync
244: // GC assistance:
245: entryForUpdate.fsd = null;
246: this .freeTreeMemoryFor(entryForUpdate.compilationRootNode);
247: entryForUpdate.compilationRootNode = null;
248:
249: //ystem.out.println("FSDU> FileStructureDependenciesUpdater has ended. " +
250: // this.dependencyUpdaterQueue.size() +
251: // " entries in queue.");
252:
253: }
254: } // startUpdate
255:
256: /**
257: * Calculate the delay as function of the number of (heavy) entries.
258: */
259: private long calculateDelayTime() {
260: // delay is in milliseconds.
261: final long numberOfEntries = this .dependencyUpdaterQueue.size();
262:
263: final long delay = 3000 / (1 + numberOfEntries
264: * numberOfEntries);
265: // 24.8.04 Changed, original line has been:
266: // final long delay = 6000 / ( 1 + numberOfEntries*numberOfEntries );
267:
268: return delay;
269: }
270:
271: public void terminate_Thread() {
272: this .doTerminate = true;
273: }
274:
275: /**
276: * Enables/disables the processing of the updater queue.
277: *
278: * Used by f.ex. the editorpanel to let other tasks, which are
279: * important for the user, be processed faster, before it's
280: * enabled again.
281: */
282: public void enableUpdater(final boolean doEnable) {
283: this .updaterIsEnabled = doEnable;
284: }
285:
286: /**
287: * This is used by the compiler thread for wake up this
288: * thread, if there still are fsd's with dependency data,
289: * which is not up to date.
290: * This shortens the wait time.
291: */
292: public synchronized void requestForProcessingAllPendingEntries() {
293: //ystem.out.println("DependenciesUpdater.requestForProcessingAllPendingEntries(): " + this.dependencyUpdaterQueue.size() + " entries.");
294: // Set the associated flag:
295: this .doProcessAllPendingEntries = true;
296: this .updaterIsEnabled = true; // in any case, see comments where
297: // updaterIsEnabled is declared.
298: this .notify();
299: // We have set the flag doProcessAllPendingEntries and notified the updater thread,
300: // so from now, the updater will run through, until all queue entries have been processed.
301: }
302:
303: /**
304: * Also called by the compiler thread: This one has to
305: * wait, until all entries here have been processed.
306: */
307: public int getNumberOfPendingQueueEntries() {
308: return this .dependencyUpdaterQueue.size();
309: }
310:
311: /**
312: * Called when the postprocessor has created or updated an FSD.
313: * Synchronized cause of the possible notify() call.
314: */
315: public synchronized void appendEntryToQueue(
316: final DependencyUpdaterQueueEntry entry) {
317: // Security: Break, if its not a project fsd:
318: if (!entry.fsd.belongsToProject) {
319: return;
320: }
321: synchronized (this .dependencyUpdaterQueue) {
322: // The passed entry is the most uptodate entry.
323: // Therefore we append this always.
324: // But if we see other entries (except the first one) for the same fsd, we remove them.
325: // Why do we exclude the first one and possibly add the same entry twice,
326: // if it equals the first one ? --> BECAUSE the first one possibly is
327: // beeing processed when the append call comes in. In this case we really
328: // have TWO subsequent dependency update tasks for the same entry.
329: // This is VERY important.
330: // We keep the first entry in the queue, until it has been processed,
331: // because we then can see simply from the size of the queue, if there
332: // are updates pending or being processed.
333: String qualifiedClassNameBuffer = entry.fsd.fullyQualifiedClassNameBuffer
334: .toString();
335: int i = 1; // Skip the first one, see explanation above.
336: while (i < this .dependencyUpdaterQueue.size()) {
337: final DependencyUpdaterQueueEntry queueEntry = (DependencyUpdaterQueueEntry) this .dependencyUpdaterQueue
338: .elementAt(i);
339: final StringBuffer queueEntryQualifiedClassNameBuffer = queueEntry.fsd.fullyQualifiedClassNameBuffer;
340: if (qualifiedClassNameBuffer
341: .equals(queueEntryQualifiedClassNameBuffer
342: .toString())) {
343: queueEntry.fsd = null; // gc assistance
344: queueEntry.compilationRootNode = null; // gc assistance
345: this .dependencyUpdaterQueue.removeElementAt(i);
346: } else {
347: i++;
348: }
349: } // while
350: this .dependencyUpdaterQueue.addElement(entry);
351: }
352: // If we have more than BusyThreshold entries in the queue,
353: // wake up the thread (!) :
354: if (this .dependencyUpdaterQueue.size() > BusyThreshold) {
355: this .notify();
356: }
357: } // appendEntryToQueue
358:
359: /**
360: * Called, when a file has been removed, so that it's not
361: * scanned after it has ben removed already (if there is
362: * an entry in here at all)
363: */
364: public void removeFileForDependencyUpdate(
365: final String fullyQualifiedClassIdentifier) {
366: synchronized (this .dependencyUpdaterQueue) {
367: int i = 0;
368: while (i < this .dependencyUpdaterQueue.size()) {
369: final DependencyUpdaterQueueEntry queueEntry = (DependencyUpdaterQueueEntry) this .dependencyUpdaterQueue
370: .elementAt(i);
371: final StringBuffer queueFSDIdentifierBuffer = queueEntry.fsd.fullyQualifiedClassNameBuffer;
372: if (fullyQualifiedClassIdentifier
373: .equals(queueFSDIdentifierBuffer.toString())) {
374: queueEntry.fsd = null;
375: this
376: .freeTreeMemoryFor(queueEntry.compilationRootNode);
377: queueEntry.compilationRootNode = null;
378: this .dependencyUpdaterQueue.removeElementAt(i);
379: } else {
380: i++;
381: }
382: }
383: }
384: } // removeFileForDependencyUpdate
385:
386: public void setForegroundMode() {
387: this .threadMode = ForegroundMode;
388: }
389:
390: public void setBackgroundMode() {
391: this .threadMode = BackgroundMode;
392: }
393:
394: /**
395: * Adds string to the passed vector, which exclusively must contain string entries,
396: * if this string is not already found in the vector.
397: */
398: private void addUniqueStringToStringVector(final String string,
399: final Vector vector) {
400: boolean isAlreadyExisting = false;
401: for (int i = 0; i < vector.size(); i++) {
402: final String s = (String) vector.elementAt(i);
403: if (s.equals(string)) {
404: isAlreadyExisting = true;
405: break;
406: }
407: }
408: if (!isAlreadyExisting) {
409: vector.addElement(string);
410: }
411: } // addUniqueStringToStringVector
412:
413: /**
414: * Recursive GC assistance
415: */
416: private void freeTreeMemoryFor(final DefaultMutableTreeNode node) {
417: if (node != null) {
418: if (node.getAllowsChildren()) {
419: for (int i = 0; i < node.getChildCount(); i++) {
420: DefaultMutableTreeNode this Child = (DefaultMutableTreeNode) node
421: .getChildAt(i);
422: this .freeTreeMemoryFor(this Child);
423: }
424: node.removeAllChildren(); // only removes level one children
425: node.removeFromParent();
426: node.setUserObject(null);
427: } else {
428: final Object userObject = node.getUserObject();
429: if (userObject != null) {
430: if (userObject instanceof TokenTreeUserObject) {
431: ((TokenTreeUserObject) userObject).freeMemory();
432: }
433: node.removeFromParent();
434: node.setUserObject(null);
435: }
436: }
437: }
438: } // freeTreeMemoryFor
439:
440: } // FileStructureDependenciesUpdater
|