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.api.project;
043:
044: import java.io.File;
045: import java.net.MalformedURLException;
046: import java.net.URI;
047: import java.net.URISyntaxException;
048: import java.net.URL;
049: import java.util.ArrayList;
050: import java.util.List;
051: import java.util.logging.Level;
052: import java.util.logging.Logger;
053: import org.netbeans.modules.projectapi.SimpleFileOwnerQueryImplementation;
054: import org.netbeans.spi.project.FileOwnerQueryImplementation;
055: import org.openide.filesystems.FileObject;
056: import org.openide.filesystems.FileUtil;
057: import org.openide.util.Lookup;
058: import org.openide.util.LookupEvent;
059: import org.openide.util.LookupListener;
060:
061: /**
062: * Find the project which owns a file.
063: * <p>
064: * There is a default implementation of {@link org.netbeans.spi.project.FileOwnerQueryImplementation}
065: * which considers a file owned by the project corresponding to the nearest enclosing
066: * project directory or marked external owner, if such a directory exists. But
067: * other implementations can be registered to lookup as well.
068: * <p>
069: * Warning: This class and it's methods may not be used within DataObject recognition in DataLoaders.
070: * eg. in {@link org.openide.loaders.MultiFileLoader#findPrimaryFile}
071: *
072: * @author Jesse Glick
073: */
074: public class FileOwnerQuery {
075:
076: // XXX acquire the appropriate ProjectManager.mutex for the duration of calls
077:
078: private static final Logger LOG = Logger
079: .getLogger(FileOwnerQuery.class.getName());
080:
081: private static Lookup.Result<FileOwnerQueryImplementation> implementations;
082:
083: /** Cache of all available FileOwnerQueryImplementation instances. */
084: private static List<FileOwnerQueryImplementation> cache;
085:
086: private FileOwnerQuery() {
087: }
088:
089: /**
090: * Find the project, if any, which "owns" the given file.
091: * @param file the file (generally on disk)
092: * @return a project which contains it, or null if there is no known project containing it
093: */
094: public static Project getOwner(FileObject file) {
095: if (file == null) {
096: throw new NullPointerException(
097: "Passed null to FileOwnerQuery.getOwner(FileObject)"); // NOI18N
098: }
099: FileObject archiveRoot = FileUtil.getArchiveFile(file);
100: if (archiveRoot != null) {
101: file = archiveRoot;
102: }
103: for (FileOwnerQueryImplementation q : getInstances()) {
104: Project p = q.getOwner(file);
105: if (p != null) {
106: LOG.log(Level.FINE, "getOwner({0}) -> {1} from {2}",
107: new Object[] { file, p, q });
108: return p;
109: }
110: }
111: LOG.log(Level.FINE, "getOwner({0}) -> nil", file);
112: return null;
113: }
114:
115: /**
116: * Find the project, if any, which "owns" the given URI.
117: * @param uri the uri to the file (generally on disk); must be absolute and not opaque
118: * @return a project which contains it, or null if there is no known project containing it
119: * @throws IllegalArgumentException if the URI is relative or opaque
120: */
121: public static Project getOwner(URI uri) {
122: if (uri.isOpaque() && "jar".equalsIgnoreCase(uri.getScheme())) { //NOI18N
123: // XXX the following is bogus; should use FileUtil methods
124: String schemaPart = uri.getSchemeSpecificPart();
125: int index = schemaPart.lastIndexOf('!'); //NOI18N
126: if (index > 0) {
127: schemaPart = schemaPart.substring(0, index);
128: }
129: // XXX: schemaPart can contains spaces. create File first and
130: // then convert it to URI.
131: try {
132: //#85137 - # character in uri path seems to cause problems, because it's not escaped. test added.
133: schemaPart = schemaPart.replace("#", "%23");
134: uri = new URI(schemaPart);
135: } catch (URISyntaxException ex) {
136: try {
137: URL u = new URL(schemaPart);
138: // XXX bad to ever use new File(URL.getPath()):
139: uri = new File(u.getPath()).toURI();
140: } catch (MalformedURLException ex2) {
141: ex2.printStackTrace();
142: assert false : schemaPart;
143: return null;
144: }
145: }
146: } else if (!uri.isAbsolute() || uri.isOpaque()) {
147: throw new IllegalArgumentException("Bad URI: " + uri); // NOI18N
148: }
149: for (FileOwnerQueryImplementation q : getInstances()) {
150: Project p = q.getOwner(uri);
151: if (p != null) {
152: LOG.log(Level.FINE, "getOwner({0}) -> {1} from {2}",
153: new Object[] { uri, p, q });
154: return p;
155: }
156: }
157: LOG.log(Level.FINE, "getOwner({0}) -> nil", uri);
158: return null;
159: }
160:
161: /**
162: * Intended for use from unit tests. Clears internal state such as
163: * external file owners.
164: */
165: static void reset() {
166: SimpleFileOwnerQueryImplementation.reset();
167: }
168:
169: /**
170: * Simplest algorithm for marking external file owners, which just keeps
171: * a transient memory cache of this information.
172: * The external marking is only guaranteed to be good during this VM session
173: * for as long as the external file root is retained in memory as a
174: * <code>FileObject</code>. For this reason, a project which makes such a
175: * marking when it is created is obliged to hold a reference to the external
176: * file root for as long as the project itself is in memory, to ensure that
177: * it continues to work.
178: */
179: public static final int EXTERNAL_ALGORITHM_TRANSIENT = 0;
180:
181: /**
182: * Mark an external folder or file as being owned by a particular project.
183: * After this call is made, for the duration appropriate to the selected
184: * algorithm, that folder or file and its ancestors will be considered owned
185: * by the project (if any) matching the named project directory, except in
186: * the case that a lower enclosing project directory can be found.
187: * <p class="nonnormative">
188: * Typical usage would be to call this method for each external source root
189: * of a project (if any) as soon as the project is loaded, if a transient
190: * algorithm is selected, or only when the project is created, if a reliable
191: * persistent algorithm is selected.
192: * </p>
193: * @param root a folder or a file which should be considered part of a project
194: * @param owner a project which should be considered to own that folder tree
195: * (any prior marked external owner is overridden),
196: * or null to cancel external ownership for this folder root
197: * @param algorithm an algorithm to use for retaining this information;
198: * currently may only be {@link #EXTERNAL_ALGORITHM_TRANSIENT}
199: * @throws IllegalArgumentException if the root or owner is null, if an unsupported
200: * algorithm is requested,
201: * if the root is already a project directory,
202: * or if the root is already equal to or inside the owner's
203: * project directory (it may however be an ancestor)
204: * @see <a href="@org-netbeans-modules-project-ant@/org/netbeans/spi/project/support/ant/SourcesHelper.html"><code>SourcesHelper</code></a>
205: */
206: public static void markExternalOwner(FileObject root,
207: Project owner, int algorithm)
208: throws IllegalArgumentException {
209: LOG.log(Level.FINE, "markExternalOwner({0}, {1}, {2})",
210: new Object[] { root, owner, algorithm });
211: switch (algorithm) {
212: case EXTERNAL_ALGORITHM_TRANSIENT:
213: // XXX check args
214: SimpleFileOwnerQueryImplementation
215: .markExternalOwnerTransient(root, owner);
216: break;
217: default:
218: throw new IllegalArgumentException("No such algorithm: "
219: + algorithm); // NOI18N
220: }
221: }
222:
223: /**
224: * Mark an external URI (folder or file) as being owned by a particular project.
225: * After this call is made, for the duration appropriate to the selected
226: * algorithm, that folder or file and its ancestors will be considered owned
227: * by the project (if any) matching the named project directory, except in
228: * the case that a lower enclosing project directory can be found.
229: * <p class="nonnormative">
230: * Typical usage would be to call this method for each external source root
231: * of a project (if any) as soon as the project is loaded, if a transient
232: * algorithm is selected, or only when the project is created, if a reliable
233: * persistent algorithm is selected.
234: * </p>
235: * @param root an URI of a folder or a file which should be considered part of a project
236: * @param owner a project which should be considered to own that folder tree
237: * (any prior marked external owner is overridden),
238: * or null to cancel external ownership for this folder root
239: * @param algorithm an algorithm to use for retaining this information;
240: * currently may only be {@link #EXTERNAL_ALGORITHM_TRANSIENT}
241: * @throws IllegalArgumentException if the root or owner is null, if an unsupported
242: * algorithm is requested,
243: * if the root is already a project directory,
244: * or if the root is already equal to or inside the owner's
245: * project directory (it may however be an ancestor)
246: * @see <a href="@org-netbeans-modules-project-ant@/org/netbeans/spi/project/support/ant/SourcesHelper.html"><code>SourcesHelper</code></a>
247: */
248: public static void markExternalOwner(URI root, Project owner,
249: int algorithm) throws IllegalArgumentException {
250: LOG.log(Level.FINE, "markExternalOwner({0}, {1}, {2})",
251: new Object[] { root, owner, algorithm });
252: switch (algorithm) {
253: case EXTERNAL_ALGORITHM_TRANSIENT:
254: // XXX check args
255: SimpleFileOwnerQueryImplementation
256: .markExternalOwnerTransient(root, owner);
257: break;
258: default:
259: throw new IllegalArgumentException("No such algorithm: "
260: + algorithm); // NOI18N
261: }
262: }
263:
264: /* TBD whether this is necessary:
265: public static FileObject getMarkedExternalOwner(FileObject root) {}
266: */
267:
268: private static synchronized List<FileOwnerQueryImplementation> getInstances() {
269: if (implementations == null) {
270: implementations = Lookup.getDefault().lookupResult(
271: FileOwnerQueryImplementation.class);
272: implementations.addLookupListener(new LookupListener() {
273: public void resultChanged(LookupEvent ev) {
274: synchronized (FileOwnerQuery.class) {
275: cache = null;
276: }
277: }
278: });
279: }
280: if (cache == null) {
281: cache = new ArrayList<FileOwnerQueryImplementation>(
282: implementations.allInstances());
283: }
284: return cache;
285: }
286:
287: }
|