Merge pull request #4118 from apache/delivery

 Sync delivery to release140 for 14-rc4
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/LspTreeViewServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/LspTreeViewServiceImpl.java
index f554c0a..288a2e3 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/LspTreeViewServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/LspTreeViewServiceImpl.java
@@ -28,6 +28,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.CompletionStage;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
 import org.eclipse.lsp4j.services.LanguageClient;
 import org.eclipse.lsp4j.services.LanguageClientAware;
@@ -48,6 +50,8 @@
  */
 @JsonSegment("nodes")
 public class LspTreeViewServiceImpl implements TreeViewService, LanguageClientAware {
+    private static final Logger LOG = Logger.getLogger(LspTreeViewServiceImpl.class.getName());
+    
     private final Lookup sessionLookup;
     /**
      * The delegate tree service.
@@ -62,6 +66,7 @@
             @Override
             protected void notifyItemChanged(NodeChangedParams itemId) {
                 if (langClient != null) {
+                    LOG.log(Level.FINER, "Firing item {0} changed", itemId);
                     langClient.notifyNodeChange(itemId);
                 }
             }
@@ -127,6 +132,7 @@
     // export const info = new ProtocolRequestType<NodeOperationParams, Data, never,void, void>('nodes/info');
     public CompletableFuture<TreeItem> info(NodeOperationParams params) {
         int nodeId = params.getNodeId();
+        LOG.log(Level.FINER, "> info({0})", nodeId);
         TreeViewProvider tvp = treeService.providerOf(nodeId);
         return tvp.getTreeItem(nodeId).toCompletableFuture();
     }
@@ -141,6 +147,7 @@
     @Override
     public CompletableFuture<int[]> getChildren(NodeOperationParams params) {
         int id = params.getNodeId();
+        LOG.log(Level.FINER, "> children({0})", id);
         TreeViewProvider tvp = treeService.providerOf(id);
         return tvp.getChildren(id).toCompletableFuture();
     }
@@ -154,6 +161,7 @@
     @Override
     public CompletableFuture<Boolean> delete(NodeOperationParams params) {
         int id = params.getNodeId();
+        LOG.log(Level.FINER, "> delete({0})", id);
         TreeViewProvider tvp = treeService.providerOf(id);
         if (tvp != null) {
             Node n = tvp.findNode(id);
@@ -186,6 +194,7 @@
     @Override
     public CompletableFuture<int[]> findPath(FindPathParams params) {
         Object toSelect = params.getSelectData();
+        LOG.log(Level.FINER, "> findPath(fromId = {0}, select = {1})", new Object[] { params.getRootNodeId(), toSelect });
         TreeViewProvider tvp = treeService.providerOf(params.getRootNodeId());
         if (tvp == null) {
             return null;
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeItem.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeItem.java
index 0c06aa7..5c43ef2 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeItem.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeItem.java
@@ -109,4 +109,11 @@
             this.resourceUri = URLMapper.findURL(fo, URLMapper.EXTERNAL).toString();
         }
     }
+    
+    public String toString() {
+        return String.format(
+            "TreeItem[%s, id = %d, resource = %s, context = %s",
+            name, id, resourceUri, contextValue
+        );
+    }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeViewProvider.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeViewProvider.java
index 7171331..4f67ccc 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeViewProvider.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/TreeViewProvider.java
@@ -34,14 +34,12 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
-import java.util.WeakHashMap;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.logging.Level;
@@ -137,46 +135,56 @@
     private SortedMap<Integer, NodeHolder> holdChildren = new TreeMap<>();
     
     /**
-     * Node > identity map.
+     * Node > identity map. Note that FilterNodes equals compare the original node, so
+     * IdentityHashMap-style map must be used.
      */
     // @GuardedBy(this)
-    private Map<Node, Integer> idMap = new WeakHashMap<>();
+    private WeakIdentityMap<Node, Integer> idMap = WeakIdentityMap.newHashMap();
     
     protected TreeViewProvider(String treeId, ExplorerManager manager, TreeNodeRegistry registry, Lookup context) {
         this.treeId = treeId;
         this.context = context;
         this.manager = manager;
+        
+        Node n;
         this.nodeRegistry = registry;
         
         this.nodeListener = new NodeListener() {
             @Override
             public void childrenAdded(NodeMemberEvent ev) {
+                LOG.log(Level.FINER, "tree {0} children of node {2} added: {1}", new Object[] { treeId, ev, ev.getNode() });
                 notifyChange(ev.getNode());
             }
 
             @Override
             public void childrenRemoved(NodeMemberEvent ev) {
+                LOG.log(Level.FINER, "tree {0} children of node {2} removed: {1}", new Object[] { treeId, ev, ev.getNode() });
                 notifyChange(ev.getNode());
             }
 
             @Override
             public void childrenReordered(NodeReorderEvent ev) {
+                LOG.log(Level.FINER, "tree {0} children of node {2} reordered: {1}", new Object[] { treeId, ev, ev.getNode() });
                 notifyChange(ev.getNode());
             }
 
             @Override
             public void nodeDestroyed(NodeEvent ev) {
+                LOG.log(Level.FINER, "tree {0} children of node {2} destroyed: {1}", new Object[] { treeId, ev, ev.getNode() });
                 removeNode(ev.getNode());
                 notifyChange(ev.getNode());
             }
 
             @Override
             public void propertyChange(PropertyChangeEvent ev) {
+                LOG.log(Level.FINER, "tree {0} property of node {2} changed: {1}", new Object[] { treeId, ev, ev.getSource()});
                 notifyChange((Node) ev.getSource());
             }
 
             private void notifyChange(Node src) {
-                onDidChangeTreeData(src, findId(src));
+                int id = findId(src);
+                LOG.log(Level.FINER, "tree {0} tree item changed: {1}", new Object[] { treeId, id});
+                onDidChangeTreeData(src, id);
             }
         };
         factories = context.lookupResult(TreeDataProvider.Factory.class);
@@ -368,6 +376,7 @@
      * @return a TreeItem suitable for LSP transmit
      */
     public TreeItem findTreeItem(Node n) {
+        LOG.log(Level.FINER, "Finding tree item for node {0}", n);
         TreeDataProvider[] pa = this.providers;
         String v;
         boolean expanded;
@@ -411,7 +420,7 @@
         if (data.getResourceURI() != null) {
             ti.resourceUri = data.getResourceURI().toString();
         }
-        
+        LOG.log(Level.FINER, "Finding tree item for node {0} => {1} ", new Object[] { n, ti });
         return ti;
     }
     
@@ -444,7 +453,7 @@
             nh.id2Child = newId2Node;
         }
         if (LOG.isLoggable(Level.FINER)) {
-            LOG.log(Level.FINER, "Children of id {0}: {1}", new Object[] { parentId, Arrays.asList(ids) });
+            LOG.log(Level.FINER, "Children of id {0}: {1}", new Object[] { parentId, Arrays.toString(ids) });
         }
         if (obsolete != null) {
             synchronized (this) {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/WeakIdentityMap.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/WeakIdentityMap.java
new file mode 100644
index 0000000..f061a03
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/explorer/WeakIdentityMap.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.explorer;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implements a combination of {@link java.util.WeakHashMap} and {@link java.util.IdentityHashMap}.
+ * Useful for caches that need to key off of a {@code ==} comparison instead of a {@code .equals}.
+ *
+ * The code was originally part of Apache Lucene
+ */
+public final class WeakIdentityMap<K, V> {
+  private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
+  private final Map<IdentityWeakReference, V> backingStore;
+  private final boolean reapOnRead;
+
+  /**
+   * Creates a new {@code WeakIdentityMap} based on a non-synchronized {@link HashMap}. The map <a
+   * href="#reapInfo">cleans up the reference queue on every read operation</a>.
+   */
+  public static <K, V> WeakIdentityMap<K, V> newHashMap() {
+    return newHashMap(true);
+  }
+
+  /**
+   * Creates a new {@code WeakIdentityMap} based on a non-synchronized {@link HashMap}.
+   *
+   * @param reapOnRead controls if the map <a href="#reapInfo">cleans up the reference queue on
+   *     every read operation</a>.
+   */
+  public static <K, V> WeakIdentityMap<K, V> newHashMap(boolean reapOnRead) {
+    return new WeakIdentityMap<>(new HashMap<IdentityWeakReference, V>(), reapOnRead);
+  }
+
+  /**
+   * Creates a new {@code WeakIdentityMap} based on a {@link ConcurrentHashMap}. The map <a
+   * href="#reapInfo">cleans up the reference queue on every read operation</a>.
+   */
+  public static <K, V> WeakIdentityMap<K, V> newConcurrentHashMap() {
+    return newConcurrentHashMap(true);
+  }
+
+  /**
+   * Creates a new {@code WeakIdentityMap} based on a {@link ConcurrentHashMap}.
+   *
+   * @param reapOnRead controls if the map <a href="#reapInfo">cleans up the reference queue on
+   *     every read operation</a>.
+   */
+  public static <K, V> WeakIdentityMap<K, V> newConcurrentHashMap(boolean reapOnRead) {
+    return new WeakIdentityMap<>(new ConcurrentHashMap<IdentityWeakReference, V>(), reapOnRead);
+  }
+
+  /** Private only constructor, to create use the static factory methods. */
+  private WeakIdentityMap(Map<IdentityWeakReference, V> backingStore, boolean reapOnRead) {
+    this.backingStore = backingStore;
+    this.reapOnRead = reapOnRead;
+  }
+
+  /** Removes all of the mappings from this map. */
+  public void clear() {
+    backingStore.clear();
+    reap();
+  }
+
+  /** Returns {@code true} if this map contains a mapping for the specified key. */
+  public boolean containsKey(Object key) {
+    if (reapOnRead) reap();
+    return backingStore.containsKey(new IdentityWeakReference(key, null));
+  }
+
+  /** Returns the value to which the specified key is mapped. */
+  public V get(Object key) {
+    if (reapOnRead) reap();
+    return backingStore.get(new IdentityWeakReference(key, null));
+  }
+
+  /**
+   * Associates the specified value with the specified key in this map. If the map previously
+   * contained a mapping for this key, the old value is replaced.
+   */
+  public V put(K key, V value) {
+    reap();
+    return backingStore.put(new IdentityWeakReference(key, queue), value);
+  }
+
+  /** Returns {@code true} if this map contains no key-value mappings. */
+  public boolean isEmpty() {
+    return size() == 0;
+  }
+
+  /**
+   * Removes the mapping for a key from this weak hash map if it is present. Returns the value to
+   * which this map previously associated the key, or {@code null} if the map contained no mapping
+   * for the key. A return value of {@code null} does not necessarily indicate that the map
+   * contained.
+   */
+  public V remove(Object key) {
+    reap();
+    return backingStore.remove(new IdentityWeakReference(key, null));
+  }
+
+  /**
+   * Returns the number of key-value mappings in this map. This result is a snapshot, and may not
+   * reflect unprocessed entries that will be removed before next attempted access because they are
+   * no longer referenced.
+   */
+  public int size() {
+    if (backingStore.isEmpty()) return 0;
+    if (reapOnRead) reap();
+    return backingStore.size();
+  }
+
+  /**
+   * Returns an iterator over all weak keys of this map. Keys already garbage collected will not be
+   * returned. This Iterator does not support removals.
+   */
+  public Iterator<K> keyIterator() {
+    reap();
+    final Iterator<IdentityWeakReference> iterator = backingStore.keySet().iterator();
+    // IMPORTANT: Don't use oal.util.FilterIterator here:
+    // We need *strong* reference to current key after setNext()!!!
+    return new Iterator<K>() {
+      // holds strong reference to next element in backing iterator:
+      private Object next = null;
+      // the backing iterator was already consumed:
+      private boolean nextIsSet = false;
+
+      @Override
+      public boolean hasNext() {
+        return nextIsSet || setNext();
+      }
+
+      @Override
+      @SuppressWarnings("unchecked")
+      public K next() {
+        if (!hasNext()) {
+          throw new NoSuchElementException();
+        }
+        assert nextIsSet;
+        try {
+          return (K) next;
+        } finally {
+          // release strong reference and invalidate current value:
+          nextIsSet = false;
+          next = null;
+        }
+      }
+
+      @Override
+      public void remove() {
+        throw new UnsupportedOperationException();
+      }
+
+      private boolean setNext() {
+        assert !nextIsSet;
+        while (iterator.hasNext()) {
+          next = iterator.next().get();
+          if (next == null) {
+            // the key was already GCed, we can remove it from backing map:
+            iterator.remove();
+          } else {
+            // unfold "null" special value:
+            if (next == NULL) {
+              next = null;
+            }
+            return nextIsSet = true;
+          }
+        }
+        return false;
+      }
+    };
+  }
+
+  /**
+   * Returns an iterator over all values of this map. This iterator may return values whose key is
+   * already garbage collected while iterator is consumed, especially if {@code reapOnRead} is
+   * {@code false}.
+   */
+  public Iterator<V> valueIterator() {
+    if (reapOnRead) reap();
+    return backingStore.values().iterator();
+  }
+
+  /**
+   * This method manually cleans up the reference queue to remove all garbage collected key/value
+   * pairs from the map. Calling this method is not needed if {@code reapOnRead = true}. Otherwise
+   * it might be a good idea to call this method when there is spare time (e.g. from a background
+   * thread).
+   *
+   * @see <a href="#reapInfo">Information about the <code>reapOnRead</code> setting</a>
+   */
+  public void reap() {
+    Reference<?> zombie;
+    while ((zombie = queue.poll()) != null) {
+      backingStore.remove(zombie);
+    }
+  }
+
+  // we keep a hard reference to our NULL key, so map supports null keys that never get GCed:
+  static final Object NULL = new Object();
+
+  private static final class IdentityWeakReference extends WeakReference<Object> {
+    private final int hash;
+
+    IdentityWeakReference(Object obj, ReferenceQueue<Object> queue) {
+      super(obj == null ? NULL : obj, queue);
+      hash = System.identityHashCode(obj);
+    }
+
+    @Override
+    public int hashCode() {
+      return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o instanceof IdentityWeakReference) {
+        final IdentityWeakReference ref = (IdentityWeakReference) o;
+        if (this.get() == ref.get()) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+}
\ No newline at end of file
diff --git a/java/java.lsp.server/vscode/CHANGELOG.md b/java/java.lsp.server/vscode/CHANGELOG.md
index 11b842e..c3e8b2d 100644
--- a/java/java.lsp.server/vscode/CHANGELOG.md
+++ b/java/java.lsp.server/vscode/CHANGELOG.md
@@ -20,6 +20,11 @@
     under the License.
 
 -->
+## Version 14.0
+* Workaround for VSCode 1.67 error which breaks Projects explorer icon
+* Remove HTML tags from project problem messages
+* Fixes for Gradle projects and LSP
+
 ## Version 13.0.301
 * Added base code completion for Spock test framework
   * Spock Block Names are offered inside methods if the class extends Spock Specification
diff --git a/java/java.lsp.server/vscode/src/explorer.ts b/java/java.lsp.server/vscode/src/explorer.ts
index 45307bd..ac99bfb 100644
--- a/java/java.lsp.server/vscode/src/explorer.ts
+++ b/java/java.lsp.server/vscode/src/explorer.ts
@@ -198,11 +198,11 @@
   public findProductIcon(res : vscode.Uri, ...values: string[]) : string | ThemeIcon | undefined {
     const s : string = res.toString();
     outer: for (let e of this.entries) {
-      if (e.uriRegexp.exec(s)) {
+      if (e.uriRegexp.test(s)) {
         if (e.valueRegexps) {
           let s : string = " " + values.join(" ") + " ";
           for (let vr of e.valueRegexps) {
-            if (!vr.exec(s)) {
+            if (!vr.test(s)) {
               continue outer;
             }
           }
@@ -402,9 +402,10 @@
     return this.wrap(async (arr) => {
       const pn : number = Number(element.parent?.id) || -1;
       let fetched = await this.queryVisualizer(element, arr, () => this.fetchItem(pn, n));
+      let origin : vscode.TreeItem;
       if (fetched) {
         element.update(fetched);
-        return self.getTreeItem2(fetched);
+        origin = await self.getTreeItem2(fetched);
       } else {
         // fire a change, this was unexpected
         const pn : number = Number(element.parent?.id) || -1;
@@ -412,8 +413,27 @@
         if (pv) {
           this.fireItemChange(pv);
         }
-        return element;
+        origin = element;
       }
+      let ti : vscode.TreeItem = new vscode.TreeItem(origin.label || "", origin.collapsibleState);
+
+      // See #4113 -- vscode broke icons display, if resourceUri is defined in TreeItem. We're OK with files,
+      // but folders can have a semantic icon, so let hide resourceUri from vscode for folders.
+      ti.command = origin.command;
+      ti.contextValue = origin.contextValue;
+      ti.description = origin.description;
+      ti.iconPath = origin.iconPath;
+      ti.id = origin.id;
+      ti.label = origin.label;
+      ti.tooltip = origin.tooltip;
+      ti.accessibilityInformation = origin.accessibilityInformation;
+
+      if (origin.resourceUri) {
+        if (!origin.resourceUri.toString().endsWith("/")) {
+          ti.resourceUri = origin.resourceUri;
+        }
+      }
+      return ti;
     });
   }
 
diff --git a/java/libs.javacapi/external/binaries-list b/java/libs.javacapi/external/binaries-list
index a6c5b19..cb152e0 100644
--- a/java/libs.javacapi/external/binaries-list
+++ b/java/libs.javacapi/external/binaries-list
@@ -14,5 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-2449F4A630A5493F55CC013CF452632AE93007CA com.dukescript.nbjavac:nb-javac:jdk-18+36:api
-DC722E7A5691677C02F315A7E669BE20C47E70B1 com.dukescript.nbjavac:nb-javac:jdk-18+36
+ECA15E615777CE6E7550F71EF312B8CEEBCBE0BD com.dukescript.nbjavac:nb-javac:jdk-18.0.1+10:api
+3AD512FBC8830D89AC70D0CA59397C4868789DCC com.dukescript.nbjavac:nb-javac:jdk-18.0.1+10
diff --git a/java/libs.javacapi/external/nb-javac-jdk-18+36-license.txt b/java/libs.javacapi/external/nb-javac-jdk-18.0.1+10-license.txt
similarity index 99%
rename from java/libs.javacapi/external/nb-javac-jdk-18+36-license.txt
rename to java/libs.javacapi/external/nb-javac-jdk-18.0.1+10-license.txt
index 6b27fab..2329bc0 100644
--- a/java/libs.javacapi/external/nb-javac-jdk-18+36-license.txt
+++ b/java/libs.javacapi/external/nb-javac-jdk-18.0.1+10-license.txt
@@ -1,7 +1,7 @@
 Name: Javac Compiler Implementation
 Description: Javac Compiler Implementation
-Version: jdk-18+36
-Files: nb-javac-jdk-18+36-api.jar nb-javac-jdk-18+36.jar
+Version: 18.0.1+10
+Files: nb-javac-jdk-18.0.1+10-api.jar nb-javac-jdk-18.0.1+10.jar
 License: GPL-2-CP
 Origin: OpenJDK (https://github.com/openjdk/jdk18)
 Source: https://github.com/openjdk/jdk18
diff --git a/java/libs.javacapi/nbproject/project.xml b/java/libs.javacapi/nbproject/project.xml
index da8430a..905d81b 100644
--- a/java/libs.javacapi/nbproject/project.xml
+++ b/java/libs.javacapi/nbproject/project.xml
@@ -40,11 +40,11 @@
             </public-packages>
             <class-path-extension>
                 <runtime-relative-path />
-                <binary-origin>external/nb-javac-jdk-18+36-api.jar</binary-origin>
+                <binary-origin>external/nb-javac-jdk-18.0.1+10-api.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path />
-                <binary-origin>external/nb-javac-jdk-18+36.jar</binary-origin>
+                <binary-origin>external/nb-javac-jdk-18.0.1+10.jar</binary-origin>
             </class-path-extension>
         </data>
     </configuration>
diff --git a/java/libs.nbjavacapi/external/binaries-list b/java/libs.nbjavacapi/external/binaries-list
index a6c5b19..cb152e0 100644
--- a/java/libs.nbjavacapi/external/binaries-list
+++ b/java/libs.nbjavacapi/external/binaries-list
@@ -14,5 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-2449F4A630A5493F55CC013CF452632AE93007CA com.dukescript.nbjavac:nb-javac:jdk-18+36:api
-DC722E7A5691677C02F315A7E669BE20C47E70B1 com.dukescript.nbjavac:nb-javac:jdk-18+36
+ECA15E615777CE6E7550F71EF312B8CEEBCBE0BD com.dukescript.nbjavac:nb-javac:jdk-18.0.1+10:api
+3AD512FBC8830D89AC70D0CA59397C4868789DCC com.dukescript.nbjavac:nb-javac:jdk-18.0.1+10
diff --git a/java/libs.nbjavacapi/external/nb-javac-jdk-18+36-license.txt b/java/libs.nbjavacapi/external/nb-javac-jdk-18.0.1+10-license.txt
similarity index 99%
rename from java/libs.nbjavacapi/external/nb-javac-jdk-18+36-license.txt
rename to java/libs.nbjavacapi/external/nb-javac-jdk-18.0.1+10-license.txt
index f4b2392..6d324e9 100644
--- a/java/libs.nbjavacapi/external/nb-javac-jdk-18+36-license.txt
+++ b/java/libs.nbjavacapi/external/nb-javac-jdk-18.0.1+10-license.txt
@@ -1,7 +1,7 @@
 Name: Javac Compiler Implementation
 Description: Javac Compiler Implementation
-Files: nb-javac-jdk-18+36-api.jar nb-javac-jdk-18+36.jar
-Version: jdk-18+36
+Files: nb-javac-jdk-18.0.1+10-api.jar nb-javac-jdk-18.0.1+10.jar
+Version: jdk-18.0.1+10
 License: GPL-2-CP
 Origin: OpenJDK (https://github.com/openjdk/jdk18)
 Source: https://github.com/openjdk/jdk18
diff --git a/java/libs.nbjavacapi/nbproject/project.properties b/java/libs.nbjavacapi/nbproject/project.properties
index eb02b90..1bd61ee 100644
--- a/java/libs.nbjavacapi/nbproject/project.properties
+++ b/java/libs.nbjavacapi/nbproject/project.properties
@@ -18,5 +18,5 @@
 javac.source=1.7
 javac.compilerargs=-Xlint -Xlint:-serial
 license.file.override=${nb_all}/nbbuild/licenses/GPL-2-CP
-release.external/nb-javac-jdk-18+36-api.jar=modules/ext/nb-javac-jdk-18-api.jar
-release.external/nb-javac-jdk-18+36.jar=modules/ext/nb-javac-jdk-18.jar
+release.external/nb-javac-jdk-18.0.1+10-api.jar=modules/ext/nb-javac-jdk-18-api.jar
+release.external/nb-javac-jdk-18.0.1+10.jar=modules/ext/nb-javac-jdk-18.jar
diff --git a/java/libs.nbjavacapi/nbproject/project.xml b/java/libs.nbjavacapi/nbproject/project.xml
index d52072e..7dc8a08 100644
--- a/java/libs.nbjavacapi/nbproject/project.xml
+++ b/java/libs.nbjavacapi/nbproject/project.xml
@@ -37,11 +37,11 @@
             <public-packages/>
             <class-path-extension>
                 <runtime-relative-path>ext/nb-javac-jdk-18-api.jar</runtime-relative-path>
-                <binary-origin>external/nb-javac-jdk-18+36-api.jar</binary-origin>
+                <binary-origin>external/nb-javac-jdk-18.0.1+10-api.jar</binary-origin>
             </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/nb-javac-jdk-18.jar</runtime-relative-path>
-                <binary-origin>external/nb-javac-jdk-18+36.jar</binary-origin>
+                <binary-origin>external/nb-javac-jdk-18.0.1+10.jar</binary-origin>
             </class-path-extension>
         </data>
     </configuration>
diff --git a/java/maven/src/org/netbeans/modules/maven/execute/defaultActionMappings.xml b/java/maven/src/org/netbeans/modules/maven/execute/defaultActionMappings.xml
index a830f62..6c1769c 100644
--- a/java/maven/src/org/netbeans/modules/maven/execute/defaultActionMappings.xml
+++ b/java/maven/src/org/netbeans/modules/maven/execute/defaultActionMappings.xml
@@ -98,7 +98,8 @@
             <packaging>*</packaging>
         </packagings>
         <goals>
-            <goal>integration-test</goal>
+            <goal>pre-integration-test</goal>
+            <goal>failsafe:integration-test</goal>
         </goals>
         <properties>
             <test>DummyToSkipUnitTests</test>
@@ -202,7 +203,8 @@
             <packaging>*</packaging>
         </packagings>
         <goals>
-            <goal>integration-test</goal>
+            <goal>pre-integration-test</goal>
+            <goal>failsafe:integration-test</goal>
         </goals>
         <properties>
             <test>DummyToSkipUnitTests</test>
diff --git a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps
index 0e830f6..47bbed8 100644
--- a/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps
+++ b/nbbuild/antsrc/org/netbeans/nbbuild/extlibs/ignored-overlaps
@@ -39,10 +39,6 @@
 # Used to parse data during build, but need to as a lib for ide cluster
 nbbuild/external/json-simple-1.1.1.jar ide/libs.json_simple/external/json-simple-1.1.1.jar
 
-# Compile only nb-javac
-java/libs.nbjavacapi/external/nb-javac-jdk-18+36-api.jar nbbuild/external/nb-javac-jdk-17+36-api.jar
-java/libs.nbjavacapi/external/nb-javac-jdk-18+36.jar nbbuild/external/nb-javac-jdk-17+36.jar
-
 # JFlex is used by multiple modules.
 webcommon/javascript2.jade/external/jflex-1.4.3.jar webcommon/javascript2.lexer/external/jflex-1.4.3.jar
 
@@ -121,8 +117,8 @@
 harness/apisupport.harness/external/launcher-12.5-distribution.zip platform/o.n.bootstrap/external/launcher-12.5-distribution.zip
 
 # only one is part of the product:
-java/libs.javacapi/external/nb-javac-jdk-18+36-api.jar java/libs.nbjavacapi/external/nb-javac-jdk-18+36-api.jar
-java/libs.javacapi/external/nb-javac-jdk-18+36.jar java/libs.nbjavacapi/external/nb-javac-jdk-18+36.jar
+java/libs.javacapi/external/nb-javac-jdk-18.0.1+10-api.jar java/libs.nbjavacapi/external/nb-javac-jdk-18.0.1+10-api.jar
+java/libs.javacapi/external/nb-javac-jdk-18.0.1+10.jar java/libs.nbjavacapi/external/nb-javac-jdk-18.0.1+10.jar
 
 # Maven and Gradle are self-contained distributions - ignoring overlaps
 platform/o.apache.commons.lang3/external/commons-lang3-3.8.1.jar java/maven.embedder/external/apache-maven-3.8.5-bin.zip
diff --git a/platform/openide.nodes/src/org/openide/nodes/AsynchChildren.java b/platform/openide.nodes/src/org/openide/nodes/AsynchChildren.java
index 93af978..52fe246 100644
--- a/platform/openide.nodes/src/org/openide/nodes/AsynchChildren.java
+++ b/platform/openide.nodes/src/org/openide/nodes/AsynchChildren.java
@@ -154,7 +154,18 @@
             setKeys (Collections.<T>emptyList());
             return;
         }
-        final int minimalCount = getNodesCount();
+        final List<Entry> entries = entrySupport().getEntries();
+        // use entries count rather than node count, as some keys may result in no
+        // nodes, but they're still add()ed.
+        
+        // TODO: the refresh is not completely correct: if the ChildFactory inserts a (really!) new
+        // key into the list of existing ones, the count-based detection causes setKeys()
+        // to be callled and the node(s) for the yet-not-reported keys will be formally deleted
+        // and later re-created. But if content is only added or unchanged, the refresh won't
+        // cause nodes to be deleted + recreated.
+        // Implementation detail: there's one extra fixed Entry (Children.AE) 
+        // that represents this dynamic node array. 
+        final int minimalCount = Math.max(entries.size() - 1, 0);
         List <T> keys = new LinkedList <T> () {
             @Override public boolean add(T e) {
                 if (cancelled || Thread.interrupted()) {
@@ -163,6 +174,7 @@
                 super.add(e);
                 LinkedList<Object> newKeys = new LinkedList<Object>(this);
                 Node n = factory.getWaitNode();
+
                 if (n != null) {
                     newKeys.add(n);
                 }
diff --git a/platform/openide.nodes/src/org/openide/nodes/Children.java b/platform/openide.nodes/src/org/openide/nodes/Children.java
index a59cbe7..1b9b218 100644
--- a/platform/openide.nodes/src/org/openide/nodes/Children.java
+++ b/platform/openide.nodes/src/org/openide/nodes/Children.java
@@ -1679,7 +1679,8 @@
     * added to the collection and if the same object is added
     * more than once it is indexed by a number.
     */
-    private abstract static class Dupl<T> implements Cloneable, Entry {
+    // package-private for tests only!
+    abstract static class Dupl<T> implements Cloneable, Entry {
         /** the key either real value or Dupl (Dupl (Dupl (... value ...)))*/
         protected Object key;
 
diff --git a/platform/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java b/platform/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java
index 124ba76..362de83 100644
--- a/platform/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java
+++ b/platform/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java
@@ -403,6 +403,61 @@
         assertEquals(4, root.getChildren().getNodes(true).length);
         assertEquals("[1, 2, 3, 4]", nodesCreated.toString());
     }
+    
+    /**
+     * Checks that if the same (equal) keys are added for the 2nd time, the nodes
+     * are not re-created. 
+     * @throws Exception 
+     */
+    public void testIncrementalRefreshSameNodes() throws Exception {
+        // negative key will not be mapped to a Node.
+        List<Integer> nodeKeys = Arrays.asList(1, 2, -1, 3, -2, 4, 5);
+        class F extends ChildFactory<Integer> {
+            Semaphore sem = new Semaphore(0);
+            
+            @Override
+            protected boolean createKeys(List<Integer> toPopulate) {
+                for (Integer i : nodeKeys) {
+                    toPopulate.add(i);
+                }
+                sem.release();
+                return true;
+            }
+
+            @Override protected Node createNodeForKey(Integer key) {
+                if (key < 0) {
+                    return null;
+                }
+                Node n = new AbstractNode(Children.LEAF);
+                n.setName(key.toString());
+                return n;
+            }
+            void refresh() {
+                refresh(false);
+            }
+        }
+        
+        F f = new F();
+        
+        Children c = Children.create(f, true);
+        Node[] first = c.getNodes(true);
+        f.sem.acquire();
+        f.sem.drainPermits();
+        
+        f.refresh();
+        Node[] second = c.getNodes();
+        // needed so that all keys are refreshed, the above getNodes() has triggered
+        // createKeys() in async thread.
+        f.sem.acquire();
+        // get the result.
+        second = c.getNodes();
+        
+        assertEquals(first.length, second.length);
+        // since keys did not change the nodes themselves should not change, too.
+        for (int i = 0; i < first.length; i++) {
+            assertSame(first[i], second[i]);
+        }
+    }
 
     public void testIncrementalNodeRemoval() throws Exception { // #211847
         final List<Integer> nodesCreated = new ArrayList<Integer>();