Hi Larry,

Larry Becker schrieb:
> Hi Sascha,
> 
>   Only potentially, but practically speaking my code is blocking the
> GUI thread, and there is nothing running to put more jobs on the
> queue.  The while loop in processQueue will keep maxRunningThreads
> running untill the queue begins to empty.  When is empty, there are no
> more render jobs to process.

This is wrong. Say you have enqueued three jobs.
The defaultRendering ThreadQueue is limited to one Thread running
so all threads are working one after each other. After the first
job has endend, the one and only Thread brings the counter to zero
because this if the only thread working on the jobs. But there are
two more in the queue. It is not important if the GUI thread is
blocked. There are two jobs more to do. And more problems arrive
if you have jobs that are _not_ in the default rendering ThreadQueue
like WMS. They are rendered in truly parallel way with a second
thread. Your 'while (default queue has running threads) sleep(time)'
loop don't test this second queue.

BTW: Didn't you mention that you place a
try { ... } catch (ArrayIndexOutOfBoundsException ex) { ...}
workaround in your SkyJUMP code? Have you ever thought about
the reasons? Looking at the code of OJ I didn't find this
'improvement' ...

>   This has been an interesting discussion, but we could go back and
> forth like this forever.  I admit that you know Threads very well, and
> that your design appears to be better.  I'm simply saying that JUMP
> ThreadQueue is not as bad as you are making it out to be, and that it
> has been working perfectly fine for years now.  Could it be improved?
> Of course, Jon's comments show he was constantly seeking improvements
> to it.  I am just cautious about replacing a critical section of code
> without adequate testing.  Give me a well tested "bad" design over an
> untested good design any day.

I totally agree with you. Never touch a running system. But the system
is not running very well here. Nobody wrote code in the past that needs
precise reliability in this thread issues. This has changed.
And as you say: It's a critical portion of the code. For this kinds
of code I demand 'nearly' (*) mathematical correctness. And talking
about business: I have a customer here how stumbled into this concrete
thread trap. Now I can show him my analysis and my enhancements and how
the issue is able to be fixed. I can send him a patch set against OJ to
make his private copy work. But I prefer to fix the problem at the root
and discuss it with the OJ team.

> Don't get me wrong, I do appreciate your hard work and contribution.
> I just need a little more time to test.

This why are talking about and I'm willing to give you this time. :-)

> regards,
> Larry


(*) Having in mind Knuth's famous quotation about correctness proves and
    tests.

Regards,
  Sascha

> 
> On 5/30/07, Sascha L. Teichmann <[EMAIL PROTECTED]> wrote:
>> Hi Larry,
>>
>> the magic word here is 'critical region'
>> Have a look a the finally block.
>> Assume the setRunningThreads(getRunningThreads() - 1) call
>> was executed and the counter went to zero. This is done in Thread A.
>> And now directly comes a context switch to your code
>> in Thread B. Your if statement thinks that there are no
>> jobs any more -> Your loop ends. Context switch back to Thread A.
>> Now the processQueue() call is executed and potentially a
>> new thread is started because the queue is not empty.
>> Counter is zero but queue not empty.
>> Thread programming is delicate, isn't it?
>>
>> - Sascha
>>
>>
>> Larry Becker schrieb:
>>> Hi Sascha,
>>>
>>>   I do not see the difference.  It seems to me that when a task is
>>> finished, the runningThreads count is decremented as in:
>>>
>>>                 try {
>>>                     runnable.run();
>>>                 } finally {
>>>                     setRunningThreads(getRunningThreads() - 1);
>>>                     processQueue();
>>>                 }
>>>
>>> So it would seem to me that when the count reaches zero that there are
>>> no more jobs.
>>>
>>> regards,
>>> Larry
>>>
>>> On 5/30/07, Sascha L. Teichmann <[EMAIL PROTECTED]> wrote:
>>>> Hi Larry,
>>>>
>>>> The method is named runningThreads(). This was my
>>>> mistake. I will add add get getRunningThreads() to
>>>> reestablish compatibility. Thanks for this hint.
>>>>
>>>> BTW: You're code:
>>>>
>>>> while (threadQueue.getRunningThreads() > 0) Thread.sleep(200);
>>>>
>>>> tests if there are running _threads_ left. This do not tell
>>>> you if there are _jobs_ to to! What you really want to know
>>>> is when the last job is done. See the difference?
>>>>
>>>> The new RenderingManager will have a new method to archive
>>>> this. It will switch the thread queue to single threaded mode
>>>> and enqueues a special Runnable at the end. Then the calling
>>>> thread is lock with a monitor. If the special Runnable is
>>>> executed the monitor is unlocked and the calling thread continues.
>>>> This is what you want.
>>>>
>>>> Switching to single thread mode has the advantage to keep
>>>> the correct z order of the layers. This is essential for
>>>> printing because you don't want to have any internal repaint()
>>>> operations. This causes overdraw. If you render to bitmap this
>>>> might be okay, but if you want to send the vector information
>>>> to the Graphics2D of the output driver this is not a good idea.
>>>>
>>>> Kind regards,
>>>>   Sascha
>>>>
>>>>
>>>> Larry Becker schrieb:
>>>>> It doesn't look like I'm going to have time to test the new
>>>>> ThreadQueue anytime soon.  I did plug it in long enough to determine
>>>>> that it broke my code that called getRunningThreads() since that
>>>>> method is no longer present.  I didn't have time to  look for the new
>>>>> method that I should use instead.  I should be able to get back to it
>>>>> in a couple of weeks (I'm instructing a class in our software next
>>>>> week).
>>>>>
>>>>> regards,
>>>>> Larry
>>>>>
>>>>> On 5/30/07, Michaël Michaud <[EMAIL PROTECTED]> wrote:
>>>>>>> Hei Saschachaël
>>>>>>>
>>>>>>> i got positive feedback from my colleaque on the changes of the
>>>>>>> threading stuff.
>>>>>>> If Michael aggrees too, you can commit
>>>>>>>
>>>>>>>
>>>>>> As far as I'm concerned, I am really pleased to see Sascha's
>>>>>> contribution to the core.
>>>>>> OJ really needs contributions from skilled programmers :-) .
>>>>>>
>>>>>> Michaël
>>>>>>
>>>>>>> stefan
>>>>>>>
>>>>>>> Sascha L. Teichmann schrieb:
>>>>>>>
>>>>>>>
>>>>>>>> Hi!
>>>>>>>>
>>>>>>>> Stefan Steiniger schrieb:
>>>>>>>>
>>>>>>>>
>>>>>>>>> Similar to Landon I have never touched threading nor heared any 
>>>>>>>>> lectures
>>>>>>>>> or read books on it. Thus .. i dont know if it turns out to be good 
>>>>>>>>> or bad.
>>>>>>>>> But i know that making things more clean is good objective. Thus, i
>>>>>>>>> support the changes. It would be good when we finally change the code,
>>>>>>>>> that you also put into the code some docs or references where you
>>>>>>>>> outline why somethings has changed. (actually I currently would opt to
>>>>>>>>> leave the old code as comment and not to remove entirely)
>>>>>>>>>
>>>>>>>>>
>>>>>>>> The ThreadQueue is JavaDoc'ed but I can add some
>>>>>>>> design notes as well. Keeping old things as comments negates
>>>>>>>> the existence of version control systems.
>>>>>>>> CVS is exactly the time machine you for this.
>>>>>>>> As I pointed out earlier having a ChangeLog would
>>>>>>>> be nice to document motivations and backgrounds for a change. Simply
>>>>>>>> generating a ChangeLog out of the commit messages is convenient but
>>>>>>>> it does not uncover the true potential of such a 'log' file.
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>> Due to my lack of knowledge i forwarded your email to a colleague who
>>>>>>>>> developed a web-service based on JUMP (which runs on a server, see 
>>>>>>>>> [1]),
>>>>>>>>> an ajax client and recently did some multiprocessor experiments for 
>>>>>>>>> the
>>>>>>>>> web-services processing.
>>>>>>>>>
>>>>>>>>>
>>>>>>>> I look forward to his comments.
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>> I hope 1) he will look on your proposal and 2) you can wait some days
>>>>>>>>> until he, Larry and Michael got an opinion.
>>>>>>>>>
>>>>>>>>>
>>>>>>>> @Michael: You ask what to change. For the moment just replace
>>>>>>>> ThreadQueue.java with one I've sent last. The first patches
>>>>>>>> are addressing the problem with ThreadQueue limited to one
>>>>>>>> Thread at a time (defaultRenderingThreadQueue in RenderingManger).
>>>>>>>> You can apply these patches too, but they are not needed any
>>>>>>>> longer. With next patch I'll send the this second thread queue
>>>>>>>> will be removed from RenderingManager entirely.
>>>>>>>>
>>>>>>>> What to test? Doing your day-to-day is the best thing
>>>>>>>> you can do. The most important thing is that the new one
>>>>>>>> behaves exactly like the old one. The magic word is compatibility.
>>>>>>>> I don't want to break the show. Use plug-ins, use many layers,
>>>>>>>> use less layer, use db and WMS layers. Do all the fancy stuff
>>>>>>>> you like to do. If the OJ freezes or something other looks odd
>>>>>>>> just tell me. :-)
>>>>>>>>
>>>>>>>> - Sascha
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>> Michaël Michaud schrieb:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> Sascha L. Teichmann a écrit :
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> Really back to topic:
>>>>>>>>>>>
>>>>>>>>>>> Find attached a replacement for ThreadQueue [1].
>>>>>>>>>>> To use it just overwrite the original one.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>> Hi Sascha :
>>>>>>>>>>
>>>>>>>>>> I think that trying to have a cleaner and more simple code is an
>>>>>>>>>> excellent goal, and I'd like to help, but I'm not sure I can 
>>>>>>>>>> understand
>>>>>>>>>> all these thread issues.
>>>>>>>>>> If you tell me exactly which classes I must replace (on ly 
>>>>>>>>>> ThreadQueue
>>>>>>>>>> or also the pieces of code from your previous mail) and what kind of
>>>>>>>>>> tests I should do (rendering different kind of layers ? mixing 
>>>>>>>>>> different
>>>>>>>>>> kind of layers), I'll try to make some more tests on my desktop.
>>>>>>>>>>
>>>>>>>>>> Thanks, for the hard work
>>>>>>>>>>
>>>>>>>>>> Michaël
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> This one works for the real parallel case of
>>>>>>>>>>> layer rendering too. Each time a thread finished
>>>>>>>>>>> executing its Runnable it looks into the queue
>>>>>>>>>>> if they are more jobs to do. This prevents unnecessary
>>>>>>>>>>> thread creations/shutdowns. If the queue is empty
>>>>>>>>>>> the worker thread is kept alive for 5 seconds waiting
>>>>>>>>>>> for new jobs. This results in a kind of thread pooling.
>>>>>>>>>>>
>>>>>>>>>>> @Larry: I've isolated my implementation and the OJ
>>>>>>>>>>> ThreadQueue and done a synthetic benchmark with a
>>>>>>>>>>> larger number of jobs (10,000+). My implementation
>>>>>>>>>>> works about two orders faster than the OJ one.
>>>>>>>>>>> But this is of little meaning because OJ only
>>>>>>>>>>> has to handle a number of jobs equal the number
>>>>>>>>>>> of layers. This will hardly hit 10,000+ ;-)
>>>>>>>>>>> But again: My mission is improve the structure not
>>>>>>>>>>> primarily the speed.
>>>>>>>>>>>
>>>>>>>>>>> I've tested the new ThreadQueue to some extent but
>>>>>>>>>>> I'am male(tm) after all ... potentially making mistakes.
>>>>>>>>>>> It would be very kind if someone test it too.
>>>>>>>>>>>
>>>>>>>>>>> My next step will be some clean up in the RenderingManager [2].
>>>>>>>>>>> I'am not sure that it is really needed to have two ThreadQueues
>>>>>>>>>>> there. The effect of the single tread one can be easily
>>>>>>>>>>> simulated with a data structure like the RunnableArrayList which
>>>>>>>>>>> I've posted already.
>>>>>>>>>>>
>>>>>>>>>>> Any comments?
>>>>>>>>>>>
>>>>>>>>>>> Yours,
>>>>>>>>>>> Sascha
>>>>>>>>>>>
>>>>>>>>>>> [1] com.vividsolutions.jump.workbench.ui.renderer.ThreadQueue
>>>>>>>>>>> [2] com.vividsolutions.jump.workbench.ui.renderer.RenderingManager
>>>>>>>>>>>
>>>>>>>>>>> Sunburned Surveyor schrieb:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> Sascha,
>>>>>>>>>>>>
>>>>>>>>>>>> Please accept my sincerest aopologies. I'm afriad my American
>>>>>>>>>>>> ignorance of other cultures is more than just a little obvious at
>>>>>>>>>>>> times.
>>>>>>>>>>>>
>>>>>>>>>>>> I believe I have made the same mistake with Jan. :]
>>>>>>>>>>>>
>>>>>>>>>>>> Please be patient with me as I learn the details of cultures across
>>>>>>>>>>>> the Pacific and Atalantic Oceans!
>>>>>>>>>>>>
>>>>>>>>>>>> The Sunburned Surveyor
>>>>>>>>>>>>
>>>>>>>>>>>> On 5/24/07, Sascha L. Teichmann <[EMAIL PROTECTED]> wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> TNX, but for the records 'he' would be more suited in my case.
>>>>>>>>>>>>>
>>>>>>>>>>>>> 'Sascha' is basically a Russian term of endearment for the
>>>>>>>>>>>>> boys name 'Alexander' but it's also used as a girls name.
>>>>>>>>>>>>>
>>>>>>>>>>>>> BTW: 'Jan' is a girls name in the US too, isn't? ;-)
>>>>>>>>>>>>>
>>>>>>>>>>>>> - Sascha
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Sunburned Surveyor schrieb:
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Sascha and Larry,
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I must admit that I am way over my head here. I haven't done much
>>>>>>>>>>>>>> thread programming in Java. (Hopefully Stefan has!)
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Sascha wrote: "My primary goal is to simplify the
>>>>>>>>>>>>>> threading code to make it more reliable in terms of time."
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> This seems like an admirable goal to me. If Larry, or a similar
>>>>>>>>>>>>>> programmer of his experience, agrees that this changes would be
>>>>>>>>>>>>>> beneficial, I say we give Sascha a shot at it. It sounds like 
>>>>>>>>>>>>>> she has
>>>>>>>>>>>>>> considered her changes carefully.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Just my two cents.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The Sunburned Surveyor
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On 5/24/07, Sascha L. Teichmann <[EMAIL PROTECTED]> wrote:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Hi Larry,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> short answer first: No, I don't have any benchmarks, yet.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> The long answer: My primary goal is to simplify the
>>>>>>>>>>>>>>> threading code to make it more reliable in terms of time.
>>>>>>>>>>>>>>> Gaining performance improvements would be a neat side effect.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Background: Multi-threading may be fine for slow layers
>>>>>>>>>>>>>>> which arrives later on the screen but for exporting
>>>>>>>>>>>>>>> the data (to print/layout e.g) it would be nice to have
>>>>>>>>>>>>>>> them arriving one after the other.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> My final goal is to have a simple switch between the normal
>>>>>>>>>>>>>>> and the serial mode. To archive that I try to carefully
>>>>>>>>>>>>>>> refactor the system doing little patches step by step
>>>>>>>>>>>>>>> not to break it. Refactoring the ThreadQueue with it's
>>>>>>>>>>>>>>> 'flaws' seems a to be a good starting point to me.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> One reason for this change is the fact that you are able
>>>>>>>>>>>>>>> to figure out if the default thread runs empty. But there
>>>>>>>>>>>>>>> is no way to figure out when the last thread ends. That
>>>>>>>>>>>>>>> are different things. A mechanism for this is planned.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Sorry, if I've shadowed my true intentions to much. Maybe
>>>>>>>>>>>>>>> I should discuss more before I send patches.  ;-)
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Back to the technical side:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> The patch needs some testing but I don't expect too much
>>>>>>>>>>>>>>> performance improvement.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> A less intrusive alternative to bind the Runnables that go to 
>>>>>>>>>>>>>>> the
>>>>>>>>>>>>>>> default ThreadQueue into one thread is to create a container
>>>>>>>>>>>>>>> which self is Runnable (see e.g. RunnableArrayList attached)
>>>>>>>>>>>>>>> and put them into an instance of this class.
>>>>>>>>>>>>>>> This container is put into multiRendererThreadQueue as a 
>>>>>>>>>>>>>>> Runnable.
>>>>>>>>>>>>>>> With this modification the defaultRendererThreadQueue can
>>>>>>>>>>>>>>> be removed (multiRendererThreadQueue renamed to
>>>>>>>>>>>>>>> defaultRendererThreadQueue). Only an idea ... I'm in discussion
>>>>>>>>>>>>>>> mode now. ;-)
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> - Sascha
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Larry Becker schrieb:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Hi Sascha,
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I read your comments and look at your code with interest.  It 
>>>>>>>>>>>>>>>> appears
>>>>>>>>>>>>>>>> to be an improved ThreadQueue implementation, but will require 
>>>>>>>>>>>>>>>> a lot of
>>>>>>>>>>>>>>>> testing to verify.  Before I invest this time, I would like to 
>>>>>>>>>>>>>>>> know what
>>>>>>>>>>>>>>>> problem it is solving.  I see your dislikes a - e, but these 
>>>>>>>>>>>>>>>> are not
>>>>>>>>>>>>>>>> really problems, only architectural critiques.  Have you done 
>>>>>>>>>>>>>>>> any
>>>>>>>>>>>>>>>> benchmarks that show that the new SingleThreadQueue speeds up
>>>>>>>>>>>>>>>> rendering?  Your logical argument that it should be more 
>>>>>>>>>>>>>>>> efficient  is
>>>>>>>>>>>>>>>> persuasive,  but  I have been surprised by Java before.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> respectfully,
>>>>>>>>>>>>>>>> Larry Becker
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On 5/23/07, *Sascha L. Teichmann* <[EMAIL PROTECTED]
>>>>>>>>>>>>>>>> <mailto:[EMAIL PROTECTED]>> wrote:
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   Hi together,
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   as some of you may already know i have my dislikes against
>>>>>>>>>>>>>>>>   ThreadQueue [1] (Hi, Larry!) see my mail [2]
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   a - It forks a new thread for any Runnable it processes.
>>>>>>>>>>>>>>>>   b - It has an ugly busy wait loop inside.
>>>>>>>>>>>>>>>>   c - The event listener for empty queue fires to often.
>>>>>>>>>>>>>>>>   d - The default ThreadQueue is some kind of thread 
>>>>>>>>>>>>>>>> serializer.
>>>>>>>>>>>>>>>>   e - The DB/WMS ThreadQueue has no public access.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   Now I've written a sub class of ThreadQueue: 
>>>>>>>>>>>>>>>> SingleThreadQueue
>>>>>>>>>>>>>>>>   (see attachment). This one deals with the issues a, b and d.
>>>>>>>>>>>>>>>>   I also attached a patch against RenderingManager [3] to 
>>>>>>>>>>>>>>>> handle e.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   The new class (to be placed in package
>>>>>>>>>>>>>>>>   com.vividsolutions.jump.workbench.ui.renderer) is a drop-in
>>>>>>>>>>>>>>>>   replacement for the default ThreadQueue in RenderingManager.
>>>>>>>>>>>>>>>>   Not for the ThreadQueue that handles the DB/WMS layers.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   Because Jon limited the number of parallel threads in default
>>>>>>>>>>>>>>>>   queue to 1 I see no reason why to fork a new thread for each
>>>>>>>>>>>>>>>>   Runnable it processes. Thread creation/shutdown is fairly
>>>>>>>>>>>>>>>>   expensive. Instead a single background thread is started
>>>>>>>>>>>>>>>>   which processes the Runnables one by one. If the thread
>>>>>>>>>>>>>>>>   is idle for 30 secs it shuts itself down. If you have a lot
>>>>>>>>>>>>>>>>   of (non-WMS/BB) layers this should improve performance
>>>>>>>>>>>>>>>>   and save some resources. The processing itself is done
>>>>>>>>>>>>>>>>   with a monitor (synchronized/wait/notify) so there is no
>>>>>>>>>>>>>>>>   busy wait any more.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   The DB/WMS ThreadQueue (real parallel threads) is left 
>>>>>>>>>>>>>>>> untouched
>>>>>>>>>>>>>>>>   for the moment. Depending on my personal schedule I will send
>>>>>>>>>>>>>>>>   a patch against this one too. Preliminary code with thread 
>>>>>>>>>>>>>>>> pooling
>>>>>>>>>>>>>>>>   exists but it needs a bit more testing.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   Find attached the new class and patches against 
>>>>>>>>>>>>>>>> RenderingManager and
>>>>>>>>>>>>>>>>   the old ThreadQueue to bring it to work.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   Comments are very welcome. :-)
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   Kind regrads,
>>>>>>>>>>>>>>>>     Sascha
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>   [1] com.vividsolutions.jump.workbench.ui.renderer.ThreadQueue
>>>>>>>>>>>>>>>>   [2]
>>>>>>>>>>>>>>>>   
>>>>>>>>>>>>>>>> http://sourceforge.net/mailarchive/message.php?msg_name=4653389E.6000706%40intevation.de
>>>>>>>>>>>>>>>>   [3] 
>>>>>>>>>>>>>>>> com.vividsolutions.jump.workbench.ui.renderer.RenderingManager
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> ------------------------------------------------------------------------
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> /*
>>>>>>>>>>>>>>>> * The Unified Mapping Platform (JUMP) is an extensible, 
>>>>>>>>>>>>>>>> interactive GUI
>>>>>>>>>>>>>>>> * for visualizing and manipulating spatial features with 
>>>>>>>>>>>>>>>> geometry and attributes.
>>>>>>>>>>>>>>>> *
>>>>>>>>>>>>>>>> * Copyright (C) 2003 Vivid Solutions
>>>>>>>>>>>>>>>> * Copyright (C) 2007 Intevation GmbH
>>>>>>>>>>>>>>>> *
>>>>>>>>>>>>>>>> * This program is free software; you can redistribute it and/or
>>>>>>>>>>>>>>>> * modify it under the terms of the GNU General Public License
>>>>>>>>>>>>>>>> * as published by the Free Software Foundation; either version 
>>>>>>>>>>>>>>>> 2
>>>>>>>>>>>>>>>> * of the License, or (at your option) any later version.
>>>>>>>>>>>>>>>> *
>>>>>>>>>>>>>>>> * This program is distributed in the hope that it will be 
>>>>>>>>>>>>>>>> useful,
>>>>>>>>>>>>>>>> * but WITHOUT ANY WARRANTY; without even the implied warranty 
>>>>>>>>>>>>>>>> of
>>>>>>>>>>>>>>>> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>>>>>>>>>>>>>>> * GNU General Public License for more details.
>>>>>>>>>>>>>>>> *
>>>>>>>>>>>>>>>> * You should have received a copy of the GNU General Public 
>>>>>>>>>>>>>>>> License
>>>>>>>>>>>>>>>> * along with this program; if not, write to the Free Software
>>>>>>>>>>>>>>>> * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
>>>>>>>>>>>>>>>> 02111-1307, USA.
>>>>>>>>>>>>>>>> *
>>>>>>>>>>>>>>>> * Suite #1A
>>>>>>>>>>>>>>>> * 2328 Government Street
>>>>>>>>>>>>>>>> * Victoria BC  V8T 5G5
>>>>>>>>>>>>>>>> * Canada
>>>>>>>>>>>>>>>> *
>>>>>>>>>>>>>>>> * (250)385-6040
>>>>>>>>>>>>>>>> * www.vividsolutions.com
>>>>>>>>>>>>>>>> */
>>>>>>>>>>>>>>>> package com.vividsolutions.jump.workbench.ui.renderer;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> import java.util.LinkedList;
>>>>>>>>>>>>>>>> import java.util.ArrayList;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> /**
>>>>>>>>>>>>>>>> * This thread queue executes at maximum N Runnables in parallel
>>>>>>>>>>>>>>>> * were N is a given number of worker threads that should be 
>>>>>>>>>>>>>>>> used.
>>>>>>>>>>>>>>>> * If N threads are running and busy each further incoming
>>>>>>>>>>>>>>>> * Runnable is queued until one of the threads has finished its 
>>>>>>>>>>>>>>>> current job.
>>>>>>>>>>>>>>>> * If a worker thread becomes idle (no more job in the queue)
>>>>>>>>>>>>>>>> * it is hold alive for 5 seconds. If during this period of time
>>>>>>>>>>>>>>>> * no new Runnable is enqueued the worker thread dies.
>>>>>>>>>>>>>>>> *
>>>>>>>>>>>>>>>> * @author Sascha L. Teichmann ([EMAIL PROTECTED])
>>>>>>>>>>>>>>>> */
>>>>>>>>>>>>>>>> public class ThreadQueue
>>>>>>>>>>>>>>>> {
>>>>>>>>>>>>>>>>      /** The time a worker thread stays alive if idle */
>>>>>>>>>>>>>>>>      public static final long WORKER_STAY_ALIVE_TIME = 5000L;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Worker thread. Fetches Runnable from the surrounding
>>>>>>>>>>>>>>>>       * ThreadQueue instance.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      protected class Worker
>>>>>>>>>>>>>>>>      extends         Thread
>>>>>>>>>>>>>>>>      {
>>>>>>>>>>>>>>>>              public void run() {
>>>>>>>>>>>>>>>>                      try {
>>>>>>>>>>>>>>>>                              for (;;) {
>>>>>>>>>>>>>>>>                                      Runnable runnable;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>                                      synchronized 
>>>>>>>>>>>>>>>> (queuedRunnables) {
>>>>>>>>>>>>>>>>                                              if 
>>>>>>>>>>>>>>>> (queuedRunnables.isEmpty()) {
>>>>>>>>>>>>>>>>                                                      
>>>>>>>>>>>>>>>> ++waitingThreads;
>>>>>>>>>>>>>>>>                                                      try {
>>>>>>>>>>>>>>>>                                                              
>>>>>>>>>>>>>>>> queuedRunnables.wait(WORKER_STAY_ALIVE_TIME);
>>>>>>>>>>>>>>>>                                                      }
>>>>>>>>>>>>>>>>                                                      catch 
>>>>>>>>>>>>>>>> (InterruptedException ie) {
>>>>>>>>>>>>>>>>                                                      }
>>>>>>>>>>>>>>>>                                                      finally {
>>>>>>>>>>>>>>>>                                                              
>>>>>>>>>>>>>>>> --waitingThreads;
>>>>>>>>>>>>>>>>                                                      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>                                                      // if 
>>>>>>>>>>>>>>>> still empty -> die!
>>>>>>>>>>>>>>>>                                                      if 
>>>>>>>>>>>>>>>> (queuedRunnables.isEmpty())
>>>>>>>>>>>>>>>>                                                              
>>>>>>>>>>>>>>>> break;
>>>>>>>>>>>>>>>>                                              }
>>>>>>>>>>>>>>>>                                              if (disposed)
>>>>>>>>>>>>>>>>                                                      break;
>>>>>>>>>>>>>>>>                                              runnable = 
>>>>>>>>>>>>>>>> (Runnable)queuedRunnables.remove();
>>>>>>>>>>>>>>>>                                      } // synchronized 
>>>>>>>>>>>>>>>> queuedRunnables
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>                                      try {
>>>>>>>>>>>>>>>>                                              runnable.run();
>>>>>>>>>>>>>>>>                                      }
>>>>>>>>>>>>>>>>                                      catch (Exception e) {
>>>>>>>>>>>>>>>>                                              
>>>>>>>>>>>>>>>> e.printStackTrace();
>>>>>>>>>>>>>>>>                                      }
>>>>>>>>>>>>>>>>                              } // for (;;)
>>>>>>>>>>>>>>>>                      }
>>>>>>>>>>>>>>>>                      finally { // guarantee that counter goes 
>>>>>>>>>>>>>>>> down
>>>>>>>>>>>>>>>>                              boolean allRunningThreadsFinished;
>>>>>>>>>>>>>>>>                              synchronized (runningThreads) {
>>>>>>>>>>>>>>>>                                      allRunningThreadsFinished 
>>>>>>>>>>>>>>>> = --runningThreads[0] == 0;
>>>>>>>>>>>>>>>>                              }
>>>>>>>>>>>>>>>>                              if (allRunningThreadsFinished)
>>>>>>>>>>>>>>>>                                      
>>>>>>>>>>>>>>>> fireAllRunningThreadsFinished();
>>>>>>>>>>>>>>>>                      }
>>>>>>>>>>>>>>>>              }
>>>>>>>>>>>>>>>>      } // class Worker
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * If the number of running threads goes down to zero
>>>>>>>>>>>>>>>>       * implementations of this interface are able to be 
>>>>>>>>>>>>>>>> informed.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public interface Listener {
>>>>>>>>>>>>>>>>              void allRunningThreadsFinished();
>>>>>>>>>>>>>>>>      } // interface Listener
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /** Number of running threads */
>>>>>>>>>>>>>>>>      protected int [] runningThreads = new int[1];
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /** max. Number of threads running parallel */
>>>>>>>>>>>>>>>>      protected int maxRunningThreads;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /** Number of threads that are currently idle */
>>>>>>>>>>>>>>>>      protected int waitingThreads;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /** The queue of Runnables jobs waiting to be run */
>>>>>>>>>>>>>>>>      protected LinkedList queuedRunnables;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /** Singals that the ThreadQueue is going to quit */
>>>>>>>>>>>>>>>>      protected boolean disposed;
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /** List of Listeners */
>>>>>>>>>>>>>>>>      protected ArrayList listeners = new ArrayList();
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Creates a ThreadQueue with one worker thread.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public ThreadQueue() {
>>>>>>>>>>>>>>>>              this(1);
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /** Creates a ThreadQueue with a given number of worker 
>>>>>>>>>>>>>>>> threads.
>>>>>>>>>>>>>>>>       * @param maxRunningThreads the max. number of threads to 
>>>>>>>>>>>>>>>> be run parallel.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public ThreadQueue(int maxRunningThreads) {
>>>>>>>>>>>>>>>>              this.maxRunningThreads = Math.max(1, 
>>>>>>>>>>>>>>>> maxRunningThreads);
>>>>>>>>>>>>>>>>              queuedRunnables = new LinkedList();
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Adds a Listener to this ThreadQueue.
>>>>>>>>>>>>>>>>       * @param listener the listener to add.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public synchronized void add(Listener listener) {
>>>>>>>>>>>>>>>>              if (listener != null)
>>>>>>>>>>>>>>>>                      listeners.add(listener);
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Removes a Listener from this ThreadQueue.
>>>>>>>>>>>>>>>>       * @param listener the listener to be removed.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public synchronized void remove(Listener listener) {
>>>>>>>>>>>>>>>>              if (listener != null)
>>>>>>>>>>>>>>>>                      listeners.add(listener);
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Informs Listeners of the fact that the number of 
>>>>>>>>>>>>>>>> running threads
>>>>>>>>>>>>>>>>       * went to zero.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      protected void fireAllRunningThreadsFinished() {
>>>>>>>>>>>>>>>>              ArrayList copy;
>>>>>>>>>>>>>>>>              synchronized (this) { copy = new 
>>>>>>>>>>>>>>>> ArrayList(listeners); }
>>>>>>>>>>>>>>>>              for (int i = copy.size()-1; i >= 0; --i)
>>>>>>>>>>>>>>>>                      
>>>>>>>>>>>>>>>> ((Listener)copy.get(i)).allRunningThreadsFinished();
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * The number of currently running worker threads.
>>>>>>>>>>>>>>>>       * @return number of currently running worker threads.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public int runningThreads() {
>>>>>>>>>>>>>>>>              synchronized (runningThreads) {
>>>>>>>>>>>>>>>>                      return runningThreads[0];
>>>>>>>>>>>>>>>>              }
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * The number of currently waiting Runnables.
>>>>>>>>>>>>>>>>       * @return number of currently waiting Runnables.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public int waitingRunnables() {
>>>>>>>>>>>>>>>>              synchronized (runningThreads) {
>>>>>>>>>>>>>>>>                      return queuedRunnables.size();
>>>>>>>>>>>>>>>>              }
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * The number of currently idle worker threads.
>>>>>>>>>>>>>>>>       * @return number of currently idle worker threads.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public int waitingThreads() {
>>>>>>>>>>>>>>>>              synchronized (queuedRunnables) {
>>>>>>>>>>>>>>>>                      return waitingThreads;
>>>>>>>>>>>>>>>>              }
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Adds a Runnables to the queue. It will be run in one
>>>>>>>>>>>>>>>>       * of the worker threads.
>>>>>>>>>>>>>>>>       * @param runnable The Runnables to add
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public void add(Runnable runnable) {
>>>>>>>>>>>>>>>>              int waiting;
>>>>>>>>>>>>>>>>              synchronized (queuedRunnables) {
>>>>>>>>>>>>>>>>                      if (disposed)
>>>>>>>>>>>>>>>>                              return;
>>>>>>>>>>>>>>>>                      waiting = waitingThreads;
>>>>>>>>>>>>>>>>                      queuedRunnables.add(runnable);
>>>>>>>>>>>>>>>>                      queuedRunnables.notify();
>>>>>>>>>>>>>>>>              }  // synchronized (queuedRunnables)
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>              synchronized (runningThreads) {
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>                      // if waitingThreads == 1 then
>>>>>>>>>>>>>>>>                      // the queuedRunnables.notify() should 
>>>>>>>>>>>>>>>> have waked it up.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>                      if (waitingThreads < 2 && 
>>>>>>>>>>>>>>>> runningThreads[0] < maxRunningThreads) {
>>>>>>>>>>>>>>>>                              ++runningThreads[0];
>>>>>>>>>>>>>>>>                              Worker w = new Worker();
>>>>>>>>>>>>>>>>                              w.setDaemon(true);
>>>>>>>>>>>>>>>>                              w.start();
>>>>>>>>>>>>>>>>                      }
>>>>>>>>>>>>>>>>              } // synchronized (runningThreads)
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Empties the queue of waiting Runnables.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public void clear() {
>>>>>>>>>>>>>>>>              synchronized (queuedRunnables) {
>>>>>>>>>>>>>>>>                      queuedRunnables.clear();
>>>>>>>>>>>>>>>>              }
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>      /**
>>>>>>>>>>>>>>>>       * Shuts down the ThreadQueue.
>>>>>>>>>>>>>>>>       */
>>>>>>>>>>>>>>>>      public void dispose() {
>>>>>>>>>>>>>>>>              synchronized (queuedRunnables) {
>>>>>>>>>>>>>>>>                      disposed = true;
>>>>>>>>>>>>>>>>                      queuedRunnables.clear();
>>>>>>>>>>>>>>>>                      // wakeup idle threads
>>>>>>>>>>>>>>>>                      queuedRunnables.notifyAll();
>>>>>>>>>>>>>>>>              }
>>>>>>>>>>>>>>>>              synchronized (this) {
>>>>>>>>>>>>>>>>                      listeners.clear();
>>>>>>>>>>>>>>>>              }
>>>>>>>>>>>>>>>>      }
>>>>>>>>>>>>>>>> }
>>>>>>>>>>>>>>>> // end of file
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> ------------------------------------------------------------------------
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> -------------------------------------------------------------------------
>>>>>>>>>>>>>>>> This SF.net email is sponsored by DB2 Express
>>>>>>>>>>>>>>>> Download DB2 Express C - the FREE version of DB2 express and 
>>>>>>>>>>>>>>>> take
>>>>>>>>>>>>>>>> control of your XML. No limits. Just data. Click to get it now.
>>>>>>>>>>>>>>>> http://sourceforge.net/powerbar/db2/
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> ------------------------------------------------------------------------
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> _______________________________________________
>>>>>>>>>>>>>>>> Jump-pilot-devel mailing list
>>>>>>>>>>>>>>>> Jump-pilot-devel@lists.sourceforge.net
>>>>>>>>>>>>>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>> -------------------------------------------------------------------------
>>>>>>>>>> This SF.net email is sponsored by DB2 Express
>>>>>>>>>> Download DB2 Express C - the FREE version of DB2 express and take
>>>>>>>>>> control of your XML. No limits. Just data. Click to get it now.
>>>>>>>>>> http://sourceforge.net/powerbar/db2/
>>>>>>>>>> _______________________________________________
>>>>>>>>>> Jump-pilot-devel mailing list
>>>>>>>>>> Jump-pilot-devel@lists.sourceforge.net
>>>>>>>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>> -------------------------------------------------------------------------
>>>>>>>>> This SF.net email is sponsored by DB2 Express
>>>>>>>>> Download DB2 Express C - the FREE version of DB2 express and take
>>>>>>>>> control of your XML. No limits. Just data. Click to get it now.
>>>>>>>>> http://sourceforge.net/powerbar/db2/
>>>>>>>>> _______________________________________________
>>>>>>>>> Jump-pilot-devel mailing list
>>>>>>>>> Jump-pilot-devel@lists.sourceforge.net
>>>>>>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>>>>>>
>>>>>>>>>
>>>>>>>> -------------------------------------------------------------------------
>>>>>>>> This SF.net email is sponsored by DB2 Express
>>>>>>>> Download DB2 Express C - the FREE version of DB2 express and take
>>>>>>>> control of your XML. No limits. Just data. Click to get it now.
>>>>>>>> http://sourceforge.net/powerbar/db2/
>>>>>>>> _______________________________________________
>>>>>>>> Jump-pilot-devel mailing list
>>>>>>>> Jump-pilot-devel@lists.sourceforge.net
>>>>>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>> -------------------------------------------------------------------------
>>>>>>> This SF.net email is sponsored by DB2 Express
>>>>>>> Download DB2 Express C - the FREE version of DB2 express and take
>>>>>>> control of your XML. No limits. Just data. Click to get it now.
>>>>>>> http://sourceforge.net/powerbar/db2/
>>>>>>> _______________________________________________
>>>>>>> Jump-pilot-devel mailing list
>>>>>>> Jump-pilot-devel@lists.sourceforge.net
>>>>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>> -------------------------------------------------------------------------
>>>>>> This SF.net email is sponsored by DB2 Express
>>>>>> Download DB2 Express C - the FREE version of DB2 express and take
>>>>>> control of your XML. No limits. Just data. Click to get it now.
>>>>>> http://sourceforge.net/powerbar/db2/
>>>>>> _______________________________________________
>>>>>> Jump-pilot-devel mailing list
>>>>>> Jump-pilot-devel@lists.sourceforge.net
>>>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>>>
>>>> -------------------------------------------------------------------------
>>>> This SF.net email is sponsored by DB2 Express
>>>> Download DB2 Express C - the FREE version of DB2 express and take
>>>> control of your XML. No limits. Just data. Click to get it now.
>>>> http://sourceforge.net/powerbar/db2/
>>>> _______________________________________________
>>>> Jump-pilot-devel mailing list
>>>> Jump-pilot-devel@lists.sourceforge.net
>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>
>>>
>> -------------------------------------------------------------------------
>> This SF.net email is sponsored by DB2 Express
>> Download DB2 Express C - the FREE version of DB2 express and take
>> control of your XML. No limits. Just data. Click to get it now.
>> http://sourceforge.net/powerbar/db2/
>> _______________________________________________
>> Jump-pilot-devel mailing list
>> Jump-pilot-devel@lists.sourceforge.net
>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>
> 
> 

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
Jump-pilot-devel mailing list
Jump-pilot-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel

Reply via email to