001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.projectapi;
043:
044: import java.io.IOException;
045: import java.lang.ref.Reference;
046: import java.lang.ref.WeakReference;
047: import java.net.MalformedURLException;
048: import java.net.URI;
049: import java.net.URISyntaxException;
050: import java.net.URL;
051: import java.util.Collection;
052: import java.util.Collections;
053: import java.util.LinkedList;
054: import java.util.Map;
055: import java.util.Set;
056: import java.util.WeakHashMap;
057: import org.netbeans.api.project.Project;
058: import org.netbeans.api.project.ProjectManager;
059: import org.netbeans.spi.project.FileOwnerQueryImplementation;
060: import org.openide.ErrorManager;
061: import org.openide.filesystems.FileObject;
062: import org.openide.filesystems.FileStateInvalidException;
063: import org.openide.filesystems.URLMapper;
064: import org.openide.util.Utilities;
065: import org.openide.util.WeakSet;
066:
067: /**
068: * Finds a project by searching the directory tree.
069: * @author Jesse Glick
070: */
071: public class SimpleFileOwnerQueryImplementation implements
072: FileOwnerQueryImplementation {
073:
074: /** Do nothing */
075: public SimpleFileOwnerQueryImplementation() {
076: }
077:
078: public Project getOwner(URI fileURI) {
079: // Try to find a FileObject for it.
080: URI test = fileURI;
081: FileObject file;
082: do {
083: file = uri2FileObject(test);
084: test = goUp(test);
085: } while (file == null && test != null);
086: if (file == null) {
087: return null;
088: }
089: return getOwner(file);
090: }
091:
092: private final Set<FileObject> warnedAboutBrokenProjects = new WeakSet<FileObject>();
093:
094: private Reference<FileObject> lastFoundKey = null;
095: private Reference<Project> lastFoundValue = null;
096:
097: /**
098: *
099: * #111892
100: */
101: public void resetLastFoundReferences() {
102: synchronized (this ) {
103: lastFoundValue = null;
104: lastFoundKey = null;
105: }
106: }
107:
108: public Project getOwner(FileObject f) {
109: while (f != null) {
110: synchronized (this ) {
111: if (lastFoundKey != null && lastFoundKey.get() == f) {
112: Project p = lastFoundValue.get();
113: if (p != null) {
114: return p;
115: }
116: }
117: }
118: boolean folder = f.isFolder();
119: if (folder) {
120: Project p;
121: try {
122: p = ProjectManager.getDefault().findProject(f);
123: } catch (IOException e) {
124: // There is a project here, but we cannot load it...
125: if (warnedAboutBrokenProjects.add(f)) { // #60416
126: ErrorManager.getDefault().notify(
127: ErrorManager.INFORMATIONAL, e);
128: }
129: return null;
130: }
131: if (p != null) {
132: synchronized (this ) {
133: lastFoundKey = new WeakReference<FileObject>(f);
134: lastFoundValue = new WeakReference<Project>(p);
135: }
136: return p;
137: }
138: }
139:
140: if (!externalOwners.isEmpty()
141: && (folder || externalRootsIncludeNonFolders)) {
142: Reference<FileObject> externalOwnersReference = externalOwners
143: .get(fileObject2URI(f));
144:
145: if (externalOwnersReference != null) {
146: FileObject externalOwner = externalOwnersReference
147: .get();
148:
149: if (externalOwner != null) {
150: try {
151: // Note: will be null if there is no such project.
152: Project p = ProjectManager.getDefault()
153: .findProject(externalOwner);
154: synchronized (this ) {
155: lastFoundKey = new WeakReference<FileObject>(
156: f);
157: lastFoundValue = new WeakReference<Project>(
158: p);
159: }
160: return p;
161: } catch (IOException e) {
162: // There is a project there, but we cannot load it...
163: ErrorManager.getDefault().notify(
164: ErrorManager.INFORMATIONAL, e);
165: return null;
166: }
167: }
168: }
169: }
170: f = f.getParent();
171: }
172: return null;
173: }
174:
175: /**
176: * Map from external source roots to the owning project directories.
177: */
178: private static final Map<URI, Reference<FileObject>> externalOwners = Collections
179: .synchronizedMap(new WeakHashMap<URI, Reference<FileObject>>());
180: private static boolean externalRootsIncludeNonFolders = false;
181: private static final Map<FileObject, Collection<URI>> project2External = Collections
182: .synchronizedMap(new WeakHashMap<FileObject, Collection<URI>>());
183:
184: /** @see FileOwnerQuery#reset */
185: public static void reset() {
186: externalOwners.clear();
187: }
188:
189: private static URI fileObject2URI(FileObject f) {
190: try {
191: return URI.create(f.getURL().toString());
192: } catch (FileStateInvalidException e) {
193: throw (IllegalArgumentException) new IllegalArgumentException(
194: e.toString()).initCause(e);
195: }
196: }
197:
198: /** @see FileOwnerQuery#markExternalOwner */
199: public static void markExternalOwnerTransient(FileObject root,
200: Project owner) {
201: markExternalOwnerTransient(fileObject2URI(root), owner);
202: }
203:
204: /** @see FileOwnerQuery#markExternalOwner */
205: public static void markExternalOwnerTransient(URI root,
206: Project owner) {
207: externalRootsIncludeNonFolders |= !root.getPath().endsWith("/");
208: if (owner != null) {
209: externalOwners.put(root, new WeakReference<FileObject>(
210: owner.getProjectDirectory()));
211: synchronized (project2External) {
212: FileObject prjDir = owner.getProjectDirectory();
213: Collection<URI> roots = project2External.get(prjDir);
214: if (roots == null) {
215: roots = new LinkedList<URI>();
216: project2External.put(prjDir, roots);
217: }
218: roots.add(root);
219: }
220: } else {
221: Reference<FileObject> ownerReference = externalOwners
222: .remove(root);
223:
224: if (ownerReference != null) {
225: FileObject ownerFO = ownerReference.get();
226:
227: if (ownerFO != null) {
228: synchronized (project2External) {
229: Collection<URI> roots = project2External
230: .get(ownerFO);
231: if (roots != null) {
232: roots.remove(root);
233: if (roots.size() == 0) {
234: project2External.remove(ownerFO);
235: }
236: }
237: }
238: }
239: }
240: }
241: }
242:
243: private static FileObject uri2FileObject(URI u) {
244: URL url;
245: try {
246: url = u.toURL();
247: } catch (MalformedURLException e) {
248: e.printStackTrace();
249: assert false : u;
250: return null;
251: }
252: return URLMapper.findFileObject(url);
253: }
254:
255: private static URI goUp(URI u) {
256: assert u.isAbsolute() : u;
257: assert u.getFragment() == null : u;
258: assert u.getQuery() == null : u;
259: // XXX isn't there any easier way to do this?
260: // Using getPath in the new path does not work; nbfs: URLs break. (#39613)
261: // On the other hand, nbfs: URLs are not really used any more, so do we care?
262: String path = u.getPath();
263: if (path == null || path.equals("/")) { // NOI18N
264: return null;
265: }
266: String us = u.toString();
267: if (us.endsWith("/")) { // NOI18N
268: us = us.substring(0, us.length() - 1);
269: assert path.endsWith("/"); // NOI18N
270: path = path.substring(0, path.length() - 1);
271: }
272: int idx = us.lastIndexOf('/');
273: assert idx != -1 : path;
274: if (path.lastIndexOf('/') == 0) {
275: us = us.substring(0, idx + 1);
276: } else {
277: us = us.substring(0, idx);
278: }
279: URI nue;
280: try {
281: nue = new URI(us);
282: } catch (URISyntaxException e) {
283: throw new AssertionError(e);
284: }
285: if (WINDOWS) {
286: String pth = nue.getPath();
287: // check that path is not "/C:" or "/"
288: if ((pth.length() == 3 && pth.endsWith(":"))
289: || (pth.length() == 1 && pth.endsWith("/"))) {
290: return null;
291: }
292: }
293: assert nue.isAbsolute() : nue;
294: assert u.toString().startsWith(nue.toString()) : "not a parent: "
295: + nue + " of " + u;
296: return nue;
297: }
298:
299: private static final boolean WINDOWS = Utilities.isWindows();
300:
301: }
|