# HG changeset patch # Parent 4eda6b2fba5a8b8810fc6c0ea6736568ac929e2f SimpleFileOwnerQueryImplementation refactoring needed for #186024. 1. You may query the owner of external URIs which do not exist. 2. Persist external owners immediately; no need to wait for shutdown. 3. Do not clear external ownership cache just because a project was not open. diff --git a/projectapi/manifest.mf b/projectapi/manifest.mf --- a/projectapi/manifest.mf +++ b/projectapi/manifest.mf @@ -1,6 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.projectapi/1 -OpenIDE-Module-Install: org/netbeans/modules/projectapi/Installer.class OpenIDE-Module-Specification-Version: 1.33 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml diff --git a/projectapi/src/org/netbeans/api/project/ProjectManager.java b/projectapi/src/org/netbeans/api/project/ProjectManager.java --- a/projectapi/src/org/netbeans/api/project/ProjectManager.java +++ b/projectapi/src/org/netbeans/api/project/ProjectManager.java @@ -47,7 +47,6 @@ import java.io.IOException; import java.lang.ref.Reference; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -60,7 +59,6 @@ import javax.swing.Icon; import org.netbeans.modules.projectapi.SimpleFileOwnerQueryImplementation; import org.netbeans.modules.projectapi.TimedWeakReference; -import org.netbeans.spi.project.FileOwnerQueryImplementation; import org.netbeans.spi.project.ProjectFactory; import org.netbeans.spi.project.ProjectFactory2; import org.netbeans.spi.project.ProjectState; @@ -571,13 +569,7 @@ if (!removedProjects.add(p)) { LOG.log(Level.WARNING, "An attempt to call notifyDeleted more than once. Project: {0}", p.getProjectDirectory()); } - //#111892 - Collection col = Lookup.getDefault().lookupAll(FileOwnerQueryImplementation.class); - for (FileOwnerQueryImplementation impl : col) { - if (impl instanceof SimpleFileOwnerQueryImplementation) { - ((SimpleFileOwnerQueryImplementation)impl).resetLastFoundReferences(); - } - } + SimpleFileOwnerQueryImplementation.resetLastFoundReferences(); // #111892 return null; } }); diff --git a/projectapi/src/org/netbeans/modules/projectapi/Installer.java b/projectapi/src/org/netbeans/modules/projectapi/Installer.java deleted file mode 100644 --- a/projectapi/src/org/netbeans/modules/projectapi/Installer.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * Contributor(s): - * - * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun - * Microsystems, Inc. All Rights Reserved. - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - */ - -package org.netbeans.modules.projectapi; - -import org.openide.modules.ModuleInstall; - -/** - * Manages a module's lifecycle. Remember that an installer is optional and - * often not needed at all. - */ -public class Installer extends ModuleInstall { - - @Override - public void restored() { - //#125582 - SimpleFileOwnerQueryImplementation.deserialize(); - } - - @Override - public void close() { - //#125582 - SimpleFileOwnerQueryImplementation.serialize(); - } -} diff --git a/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java b/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java --- a/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java +++ b/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java @@ -49,15 +49,7 @@ import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.BackingStoreException; @@ -69,203 +61,146 @@ import org.openide.filesystems.FileStateInvalidException; import org.openide.filesystems.URLMapper; import org.openide.util.NbPreferences; -import org.openide.util.Utilities; -import org.openide.util.WeakSet; +import org.openide.util.lookup.ServiceProvider; /** * Finds a project by searching the directory tree. * @author Jesse Glick */ -@org.openide.util.lookup.ServiceProvider(service=org.netbeans.spi.project.FileOwnerQueryImplementation.class, position=100) +@ServiceProvider(service=FileOwnerQueryImplementation.class, position=100) public class SimpleFileOwnerQueryImplementation implements FileOwnerQueryImplementation { private static final Logger LOG = Logger.getLogger(SimpleFileOwnerQueryImplementation.class.getName()); - /** Do nothing */ - public SimpleFileOwnerQueryImplementation() {} + private static URI lastFoundKey = null; + private static Reference lastFoundValue = null; - public Project getOwner(URI fileURI) { - // Try to find a FileObject for it. - URI test = fileURI; - FileObject file; - do { - file = uri2FileObject(test); - test = goUp(test); - } while (file == null && test != null); - if (file == null) { - return null; - } - return getOwner(file); + public static synchronized void resetLastFoundReferences() { // #111892 + lastFoundValue = null; + lastFoundKey = null; } - private final Set warnedAboutBrokenProjects = new WeakSet(); - - private Reference lastFoundKey = null; - private Reference lastFoundValue = null; - - /** - * - * #111892 - */ - public void resetLastFoundReferences() { - synchronized (this) { - lastFoundValue = null; - lastFoundKey = null; + public @Override Project getOwner(FileObject f) { + return getOwner(fileObject2URI(f), f); + } + + public @Override Project getOwner(URI u) { + return getOwner(u, uri2FileObject(u)); + } + + private static Project getOwner(URI u, FileObject f) { + synchronized (SimpleFileOwnerQueryImplementation.class) { + if (u.equals(lastFoundKey)) { + Project p = lastFoundValue.get(); + if (p != null) { + return p; + } + } } + Project p = findOwner(u.toString(), f); + if (p != null) { + synchronized (SimpleFileOwnerQueryImplementation.class) { + lastFoundKey = u; + lastFoundValue = new WeakReference(p); + } + } + return p; } - - - public Project getOwner(FileObject f) { - while (f != null) { - synchronized (this) { - if (lastFoundKey != null && lastFoundKey.get() == f) { - Project p = lastFoundValue.get(); + + private static Project findOwner(String u, FileObject f) { + // Would be nice to maintain a SortedMap of external owners and use headMap + // to jump directly to the right spot. Unfortunately we also need to inspect intermediate + // existing folders (if they exist on disk) to see if they are project directories. + if (f != null && f.isFolder()) { + try { + Project p = ProjectManager.getDefault().findProject(f); + if (p != null) { + return p; + } + } catch (IOException x) { + LOG.log(Level.FINE, "Cannot load project", x); + return null; + } + } + String externalOwner = storage().get(u, null); + if (externalOwner != null) { + FileObject externalOwnerFolder = uri2FileObject(URI.create(externalOwner)); + if (externalOwnerFolder != null && externalOwnerFolder.isFolder()) { + try { + Project p = ProjectManager.getDefault().findProject(externalOwnerFolder); if (p != null) { return p; } + } catch (IOException x) { + LOG.log(Level.FINE, "Cannot load external owner", x); } } - boolean folder = f.isFolder(); - if (folder) { - Project p; - try { - p = ProjectManager.getDefault().findProject(f); - } catch (IOException e) { - // There is a project here, but we cannot load it... - if (warnedAboutBrokenProjects.add(f)) { // #60416 - LOG.log(Level.FINE, "Cannot load project.", e); //NOI18N - } - return null; - } - if (p != null) { - synchronized (this) { - lastFoundKey = new WeakReference(f); - lastFoundValue = new WeakReference(p); - } - return p; - } + // Registration is stale - no such folder, no such project, or broken. + storage().remove(u); + } + // OK, check next level up. + int slash = u.lastIndexOf('/'); + if (slash == u.length() - 1) { // folder + slash = u.lastIndexOf('/', slash - 1); + } else if (slash == -1) { // at root? + return null; + } // else file + String u2 = u.substring(0, slash + 1); // file:/tmp/foo/ or file:/tmp/foo -> file:/tmp/ + FileObject f2 = null; + if (f != null) { + f2 = f.getParent(); + if (f2 == null) { + // At filesystem root, no matches. + return null; } - - if (!externalOwners.isEmpty() && (folder || externalRootsIncludeNonFolders)) { - URI externalOwnersURI = externalOwners.get(fileObject2URI(f)); - - if (externalOwnersURI != null) { - FileObject externalOwner = uri2FileObject(externalOwnersURI); - - if (externalOwner != null && externalOwner.isValid()) { - try { - // Note: will be null if there is no such project. - Project p = ProjectManager.getDefault().findProject(externalOwner); - synchronized (this) { - lastFoundKey = new WeakReference(f); - lastFoundValue = new WeakReference(p); - } - return p; - } catch (IOException e) { - // There is a project there, but we cannot load it... - LOG.log(Level.FINE, "Cannot load project.", e); //NOI18N - return null; - } - } - } - } - if (!deserializedExternalOwners.isEmpty() && (folder || externalRootsIncludeNonFolders)) { - FileObject externalOwner = deserializedExternalOwners.get(fileObject2URI(f)); - if (externalOwner != null && externalOwner.isValid()) { - try { - // Note: will be null if there is no such project. - Project p = ProjectManager.getDefault().findProject(externalOwner); - synchronized (this) { - lastFoundKey = new WeakReference(f); - lastFoundValue = new WeakReference(p); - } - return p; - } catch (IOException e) { - // There is a project there, but we cannot load it... - LOG.log(Level.FINE, "Cannot load project.", e); //NOI18N - return null; - } - } - } - - f = f.getParent(); + } else { + f2 = uri2FileObject(URI.create(u2)); // may still be null } - return null; + return findOwner(u2, f2); } - /** - * Map from external source roots to the owning project directories. - */ - private static final Map externalOwners = - Collections.synchronizedMap(new HashMap()); - - private static final Map deserializedExternalOwners = - Collections.synchronizedMap(new HashMap()); - - private static boolean externalRootsIncludeNonFolders = false; - - - static void deserialize() { - try { - Preferences p = NbPreferences.forModule(SimpleFileOwnerQueryImplementation.class).node("externalOwners"); - for (String name : p.keys()) { - URL u = new URL(p.get(name, null)); - URI i = new URI(name); - deserializedExternalOwners.put(i, URLMapper.findFileObject(u)); - } - } catch (Exception ex) { - LOG.log(Level.INFO, null, ex); - } - try { - NbPreferences.forModule(SimpleFileOwnerQueryImplementation.class).node("externalOwners").removeNode(); - } catch (BackingStoreException ex) { - LOG.log(Level.INFO, null, ex); - } - } - - static void serialize() { - try { - Preferences p = NbPreferences.forModule(SimpleFileOwnerQueryImplementation.class).node("externalOwners"); - for (URI uri : externalOwners.keySet()) { - URI ownerURI = externalOwners.get(uri); - p.put(uri.toString(), ownerURI.toString()); - } - p.sync(); // #184310 - } catch (Exception ex) { - LOG.log(Level.WARNING, null, ex); - } - + private static Preferences storage() { + return NbPreferences.forModule(SimpleFileOwnerQueryImplementation.class).node("externalOwners"); } /** @see FileOwnerQuery#reset */ public static void reset() { - externalOwners.clear(); + try { + storage().removeNode(); + } catch (BackingStoreException x) { + assert false : x; + } + } + + /** @see FileOwnerQuery#markExternalOwner */ + public static void markExternalOwnerTransient(FileObject root, Project owner) { + try { + markExternalOwnerTransient(root.getURL().toString(), owner); + } catch (FileStateInvalidException x) { + throw new IllegalArgumentException(x); + } + } + + /** @see FileOwnerQuery#markExternalOwner */ + public static void markExternalOwnerTransient(URI root, Project owner) { + markExternalOwnerTransient(root.toString(), owner); + } + + private static void markExternalOwnerTransient(String root, Project owner) { + if (owner != null) { + storage().put(root, fileObject2URI(owner.getProjectDirectory()).toString()); + } else { + storage().remove(root); + } } private static URI fileObject2URI(FileObject f) { try { return URI.create(f.getURL().toString()); - } catch (FileStateInvalidException e) { - throw (IllegalArgumentException) new IllegalArgumentException(e.toString()).initCause(e); + } catch (FileStateInvalidException x) { + throw new IllegalArgumentException(x); } } - - /** @see FileOwnerQuery#markExternalOwner */ - public static void markExternalOwnerTransient(FileObject root, Project owner) { - markExternalOwnerTransient(fileObject2URI(root), owner); - } - - /** @see FileOwnerQuery#markExternalOwner */ - public static void markExternalOwnerTransient(URI root, Project owner) { - externalRootsIncludeNonFolders |= !root.getPath().endsWith("/"); - if (owner != null) { - FileObject fo = owner.getProjectDirectory(); - externalOwners.put(root, fileObject2URI(fo)); - deserializedExternalOwners.remove(root); - } else { - externalOwners.remove(root); - } - } - + private static FileObject uri2FileObject(URI u) { URL url; try { @@ -278,48 +213,4 @@ return URLMapper.findFileObject(url); } - private static URI goUp(URI u) { - assert u.isAbsolute() : u; - assert u.getFragment() == null : u; - assert u.getQuery() == null : u; - // XXX isn't there any easier way to do this? - // Using getPath in the new path does not work; nbfs: URLs break. (#39613) - // On the other hand, nbfs: URLs are not really used any more, so do we care? - String path = u.getPath(); - if (path == null || path.equals("/")) { // NOI18N - return null; - } - String us = u.toString(); - if (us.endsWith("/")) { // NOI18N - us = us.substring(0, us.length() - 1); - assert path.endsWith("/"); // NOI18N - path = path.substring(0, path.length() - 1); - } - int idx = us.lastIndexOf('/'); - assert idx != -1 : path; - if (path.lastIndexOf('/') == 0) { - us = us.substring(0, idx + 1); - } else { - us = us.substring(0, idx); - } - URI nue; - try { - nue = new URI(us); - } catch (URISyntaxException e) { - throw new AssertionError(e); - } - if (WINDOWS) { - String pth = nue.getPath(); - // check that path is not "/C:" or "/" - if ((pth.length() == 3 && pth.endsWith(":")) || - (pth.length() == 1 && pth.endsWith("/"))) { - return null; - } - } - assert nue.isAbsolute() : nue; - assert u.toString().startsWith(nue.toString()) : "not a parent: " + nue + " of " + u; - return nue; - } - private static final boolean WINDOWS = Utilities.isWindows(); - } diff --git a/projectapi/test/unit/src/org/netbeans/api/project/FileOwnerQueryTest.java b/projectapi/test/unit/src/org/netbeans/api/project/FileOwnerQueryTest.java --- a/projectapi/test/unit/src/org/netbeans/api/project/FileOwnerQueryTest.java +++ b/projectapi/test/unit/src/org/netbeans/api/project/FileOwnerQueryTest.java @@ -44,6 +44,7 @@ package org.netbeans.api.project; +import java.io.File; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.net.URI; @@ -269,6 +270,26 @@ //XXX: unmarking files. } + public void testExternalOwnerNonexistentFile() throws Exception { + URI unbuilt = FileUtil.urlForArchiveOrDir(new File(getWorkDir(), "unbuilt")).toURI(); + assert unbuilt.toString().endsWith("/"); + FileOwnerQuery.markExternalOwner(unbuilt, p, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + URI subfolder = unbuilt.resolve("subfolder/"); + assert subfolder.toString().endsWith("/"); + Project p2 = ProjectManager.getDefault().findProject(subprojdir); + FileOwnerQuery.markExternalOwner(subfolder, p2, FileOwnerQuery.EXTERNAL_ALGORITHM_TRANSIENT); + assertEquals(p, FileOwnerQuery.getOwner(unbuilt)); + assertEquals(p, FileOwnerQuery.getOwner(unbuilt.resolve("stuff"))); + assertEquals(p2, FileOwnerQuery.getOwner(subfolder)); + assertEquals(p2, FileOwnerQuery.getOwner(subfolder.resolve("stuff"))); + FileUtil.createFolder(scratch, "unbuilt/stuff/testproject"); + assertEquals(p, FileOwnerQuery.getOwner(unbuilt)); + Project other = FileOwnerQuery.getOwner(unbuilt.resolve("stuff")); + assertNotNull(other); + assertEquals("stuff", other.getProjectDirectory().getName()); + assertEquals(null, FileOwnerQuery.getOwner(URI.create("file:/completely/nonexistent/"))); + } + public void testExternalOwnerDisappearingProject() throws Exception { FileObject ext1 = scratch.getFileObject("external1"); FileObject tempPrjMarker = FileUtil.createFolder(scratch, "tempprj/testproject");