[569148] Provide an OMJob that can run in stand-alone or as Eclipse job

https://bugs.eclipse.org/bugs/show_bug.cgi?id=569148
diff --git a/plugins/org.eclipse.net4j.util/.settings/.api_filters b/plugins/org.eclipse.net4j.util/.settings/.api_filters
index 8cf3425..b34473e 100644
--- a/plugins/org.eclipse.net4j.util/.settings/.api_filters
+++ b/plugins/org.eclipse.net4j.util/.settings/.api_filters
@@ -216,6 +216,14 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="src/org/eclipse/net4j/util/om/job/OMJob.java" type="org.eclipse.net4j.util.om.job.OMJob">
+        <filter id="576720909">
+            <message_arguments>
+                <message_argument value="InternalOMJob"/>
+                <message_argument value="OMJob"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/net4j/util/om/log/Logger.java" type="org.eclipse.net4j.util.om.log.Logger">
         <filter id="576725006">
             <message_arguments>
diff --git a/plugins/org.eclipse.net4j.util/META-INF/MANIFEST.MF b/plugins/org.eclipse.net4j.util/META-INF/MANIFEST.MF
index 8d064db..5399712 100644
--- a/plugins/org.eclipse.net4j.util/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.net4j.util/META-INF/MANIFEST.MF
@@ -35,6 +35,7 @@
  org.eclipse.net4j.util.io;version="3.13.0",
  org.eclipse.net4j.util.lifecycle;version="3.13.0",
  org.eclipse.net4j.util.om;version="3.13.0",
+ org.eclipse.net4j.util.om.job;version="3.13.0",
  org.eclipse.net4j.util.om.log;version="3.13.0",
  org.eclipse.net4j.util.om.monitor;version="3.13.0",
  org.eclipse.net4j.util.om.pref;version="3.13.0",
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/bundle/AbstractPlatform.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/bundle/AbstractPlatform.java
index 2d2c5d2..6857c02 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/bundle/AbstractPlatform.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/bundle/AbstractPlatform.java
@@ -10,6 +10,7 @@
  */
 package org.eclipse.net4j.internal.util.bundle;
 
+import org.eclipse.net4j.internal.util.om.InternalOMJob;
 import org.eclipse.net4j.internal.util.om.LegacyPlatform;
 import org.eclipse.net4j.internal.util.om.OSGiPlatform;
 import org.eclipse.net4j.util.collection.ConcurrentArray;
@@ -434,6 +435,12 @@
 
   protected abstract void setDebugOption(String bundleID, String option, String value);
 
+  public abstract void scheduleJob(InternalOMJob job);
+
+  public abstract void cancelJob(InternalOMJob job);
+
+  public abstract void renameJob(InternalOMJob job, String name);
+
   /**
    * TODO Make configurable via system property
    */
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/InternalOMJob.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/InternalOMJob.java
new file mode 100644
index 0000000..4446a49
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/InternalOMJob.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.net4j.internal.util.om;
+
+import org.eclipse.net4j.internal.util.bundle.AbstractPlatform;
+import org.eclipse.net4j.util.om.OMPlatform;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @author Eike Stepper
+ */
+public abstract class InternalOMJob
+{
+  private String name;
+
+  private boolean system;
+
+  public InternalOMJob(String name)
+  {
+    this.name = name;
+  }
+
+  public String getName()
+  {
+    return name;
+  }
+
+  public void setName(String name)
+  {
+    this.name = name;
+    ((AbstractPlatform)OMPlatform.INSTANCE).renameJob(this, name);
+  }
+
+  public boolean isSystem()
+  {
+    return system;
+  }
+
+  public void setSystem(boolean system)
+  {
+    this.system = system;
+  }
+
+  protected final void internalSchedule()
+  {
+    ((AbstractPlatform)OMPlatform.INSTANCE).scheduleJob(this);
+  }
+
+  protected final void internalCancel()
+  {
+    ((AbstractPlatform)OMPlatform.INSTANCE).cancelJob(this);
+  }
+
+  protected abstract IStatus run(IProgressMonitor monitor);
+}
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyBundle.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyBundle.java
index 5705b81..b84a0e4 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyBundle.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyBundle.java
@@ -275,6 +275,15 @@
       {
         url = trimSegments(url, 1);
       }
+      else if ("classes".equals(lastSegment)) //$NON-NLS-1$
+      {
+        URL url1 = trimSegments(url, 1);
+        lastSegment = lastSegment(url1);
+        if ("target".equals(lastSegment)) //$NON-NLS-1$
+        {
+          url = trimSegments(url1, 1);
+        }
+      }
 
       try
       {
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyPlatform.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyPlatform.java
index af7f1b6..211c2cd 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyPlatform.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/LegacyPlatform.java
@@ -11,18 +11,28 @@
 package org.eclipse.net4j.internal.util.om;
 
 import org.eclipse.net4j.internal.util.bundle.AbstractPlatform;
+import org.eclipse.net4j.util.concurrent.ConcurrencyUtil;
+import org.eclipse.net4j.util.container.IPluginContainer;
 import org.eclipse.net4j.util.om.LegacyUtil;
 import org.eclipse.net4j.util.om.OMBundle;
 
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
 
 /**
  * @author Eike Stepper
  */
 public class LegacyPlatform extends AbstractPlatform
 {
-  private Map<String, String> debugOptions = new ConcurrentHashMap<>(0);
+  private final Map<String, String> debugOptions = new ConcurrentHashMap<>(0);
+
+  private final Map<InternalOMJob, IProgressMonitor> jobMonitors = Collections.synchronizedMap(new HashMap<>());
 
   public LegacyPlatform()
   {
@@ -58,6 +68,41 @@
     return LegacyUtil.getCommandLineArgs();
   }
 
+  @Override
+  public void scheduleJob(InternalOMJob job)
+  {
+    IProgressMonitor monitor = new NullProgressMonitor();
+    jobMonitors.put(job, monitor);
+
+    ExecutorService threadPool = ConcurrencyUtil.getExecutorService(IPluginContainer.INSTANCE);
+    threadPool.submit(() -> {
+      try
+      {
+        job.run(monitor);
+      }
+      finally
+      {
+        jobMonitors.remove(job);
+      }
+    });
+  }
+
+  @Override
+  public void cancelJob(InternalOMJob job)
+  {
+    IProgressMonitor monitor = jobMonitors.get(job);
+    if (monitor != null)
+    {
+      monitor.setCanceled(true);
+    }
+  }
+
+  @Override
+  public void renameJob(InternalOMJob job, String name)
+  {
+    // Do nothing.
+  }
+
   public static boolean clearDebugOptions()
   {
     if (INSTANCE instanceof LegacyPlatform)
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/OSGiPlatform.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/OSGiPlatform.java
index 4642087..7c78983 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/OSGiPlatform.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/om/OSGiPlatform.java
@@ -13,17 +13,26 @@
 import org.eclipse.net4j.internal.util.bundle.AbstractPlatform;
 import org.eclipse.net4j.util.om.OMBundle;
 
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.osgi.service.debug.DebugOptions;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * @author Eike Stepper
  */
 public class OSGiPlatform extends AbstractPlatform
 {
+  private final Map<InternalOMJob, Job> eclipseJobs = Collections.synchronizedMap(new HashMap<>());
+
   BundleContext systemContext;
 
   public OSGiPlatform(Object systemContext)
@@ -70,6 +79,51 @@
   }
 
   @Override
+  public void scheduleJob(InternalOMJob job)
+  {
+    Job eclipseJob = new Job(job.getName())
+    {
+      @Override
+      protected IStatus run(IProgressMonitor monitor)
+      {
+        try
+        {
+          return job.run(monitor);
+        }
+        finally
+        {
+          eclipseJobs.remove(job);
+        }
+      }
+    };
+
+    eclipseJobs.put(job, eclipseJob);
+
+    eclipseJob.setSystem(job.isSystem());
+    eclipseJob.schedule();
+  }
+
+  @Override
+  public void cancelJob(InternalOMJob job)
+  {
+    Job eclipseJob = eclipseJobs.get(job);
+    if (eclipseJob != null)
+    {
+      eclipseJob.cancel();
+    }
+  }
+
+  @Override
+  public void renameJob(InternalOMJob job, String name)
+  {
+    Job eclipseJob = eclipseJobs.get(job);
+    if (eclipseJob != null)
+    {
+      eclipseJob.setName(name);
+    }
+  }
+
+  @Override
   protected OMBundle createBundle(String bundleID, Class<?> accessor)
   {
     return new OSGiBundle(this, bundleID, accessor);
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/TaskQueue.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/TaskQueue.java
index 7bb90ab..6d4edf4 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/TaskQueue.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/TaskQueue.java
@@ -11,6 +11,7 @@
 package org.eclipse.net4j.util.concurrent;
 
 import org.eclipse.net4j.internal.util.bundle.OM;
+import org.eclipse.net4j.util.om.job.OMJob;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
@@ -32,7 +33,7 @@
 {
   private final Set<T> queue = new LinkedHashSet<>();
 
-  private Job job;
+  private TaskJob job;
 
   public TaskQueue()
   {
@@ -62,7 +63,7 @@
       }
       else
       {
-        job = new Job(task);
+        job = new TaskJob(task);
         job.schedule();
       }
     }
@@ -83,11 +84,11 @@
   /**
    * @author Eike Stepper
    */
-  private final class Job extends org.eclipse.core.runtime.jobs.Job
+  private final class TaskJob extends OMJob
   {
     private T currentTask;
 
-    public Job(T task)
+    public TaskJob(T task)
     {
       super(getJobName(task));
       currentTask = task;
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/om/job/OMJob.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/om/job/OMJob.java
new file mode 100644
index 0000000..cec3fc3
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/om/job/OMJob.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.net4j.util.om.job;
+
+import org.eclipse.net4j.internal.util.om.InternalOMJob;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @author Eike Stepper
+ * @since 3.13
+ */
+public abstract class OMJob extends InternalOMJob
+{
+  public OMJob(String name)
+  {
+    super(name);
+  }
+
+  @Override
+  public final String getName()
+  {
+    return super.getName();
+  }
+
+  @Override
+  public final void setName(String name)
+  {
+    super.setName(name);
+  }
+
+  @Override
+  public final boolean isSystem()
+  {
+    return super.isSystem();
+  }
+
+  @Override
+  public final void setSystem(boolean system)
+  {
+    super.setSystem(system);
+  }
+
+  public final void schedule()
+  {
+    internalSchedule();
+  }
+
+  public final void cancel()
+  {
+    internalCancel();
+  }
+
+  @Override
+  protected abstract IStatus run(IProgressMonitor monitor);
+}