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.beans.PropertyChangeListener;
045: import java.util.Collections;
046: import java.util.HashMap;
047: import java.util.HashSet;
048: import java.util.Map;
049: import java.util.Set;
050: import javax.swing.Icon;
051: import javax.swing.ImageIcon;
052: import org.netbeans.spi.project.SubprojectProvider;
053: import org.netbeans.spi.project.support.GenericSources;
054: import org.openide.filesystems.FileStateInvalidException;
055: import org.openide.util.Mutex;
056: import org.openide.util.Utilities;
057:
058: /**
059: * Utility methods to get information about {@link Project}s.
060: * @author Jesse Glick
061: */
062: public class ProjectUtils {
063:
064: private ProjectUtils() {
065: }
066:
067: /**
068: * Get basic information about a project.
069: * If the project has a {@link ProjectInformation} instance in its lookup,
070: * that is used. Otherwise, a basic dummy implementation is returned.
071: * @param p a project
072: * @return some information about it
073: * @see Project#getLookup
074: */
075: public static ProjectInformation getInformation(Project p) {
076: ProjectInformation pi = p.getLookup().lookup(
077: ProjectInformation.class);
078: if (pi != null) {
079: return pi;
080: } else {
081: return new BasicInformation(p);
082: }
083: }
084:
085: /**
086: * Get a list of sources for a project.
087: * If the project has a {@link Sources} instance in its lookup,
088: * that is used. Otherwise, a basic implementation is returned
089: * using {@link GenericSources#genericOnly}.
090: * @param p a project
091: * @return a list of sources for it
092: * @see Project#getLookup
093: */
094: public static Sources getSources(Project p) {
095: Sources s = p.getLookup().lookup(Sources.class);
096: if (s != null) {
097: return s;
098: } else {
099: return GenericSources.genericOnly(p);
100: }
101: }
102:
103: /**
104: * Check whether a project has, or might have, cycles in its subproject graph.
105: * <p>
106: * If the candidate parameter is null, this simply checks whether the master
107: * project's current directed graph of (transitive) subprojects contains any
108: * cycles. If the candidate is also passed, this checks whether the master
109: * project's subproject graph would contain cycles if the candidate were added
110: * as a (direct) subproject of the master project.
111: * </p>
112: * <p>
113: * All cycles are reported even if they do not contain the master project.
114: * </p>
115: * <p>
116: * If the master project already contains the candidate as a (direct) subproject,
117: * the effect is as if the candidate were null.
118: * </p>
119: * <p>
120: * Projects with no {@link SubprojectProvider} are considered to have no
121: * subprojects, just as if the provider returned an empty set.
122: * </p>
123: * <p>
124: * Acquires read access.
125: * </p>
126: * <p class="nonnormative">
127: * Project types which let the user somehow configure subprojects in the GUI
128: * (perhaps indirectly, e.g. via a classpath) should use this call to check
129: * for possible cycles before adding new subprojects.
130: * </p>
131: * @param master a project to root the subproject graph from
132: * @param candidate a potential direct subproject of the master project, or null
133: * @return true if the master project currently has a cycle somewhere in its
134: * subproject graph, regardless of the candidate parameter, or if the
135: * candidate is not null and the master project does not currently have
136: * a cycle but would have one if the candidate were added as a subproject
137: * @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=43845">Issue #43845</a>
138: */
139: public static boolean hasSubprojectCycles(final Project master,
140: final Project candidate) {
141: return ProjectManager.mutex().readAccess(
142: new Mutex.Action<Boolean>() {
143: public Boolean run() {
144: return visit(
145: new HashSet<Project>(),
146: new HashMap<Project, Set<? extends Project>>(),
147: master, master, candidate);
148: }
149: });
150: }
151:
152: /**
153: * Do a DFS traversal checking for cycles.
154: * @param encountered projects already encountered in the DFS (added and removed as you go)
155: * @param curr current node to visit
156: * @param master the original master project (for use with candidate param)
157: * @param candidate a candidate added subproject for master, or null
158: */
159: private static boolean visit(Set<Project> encountered,
160: Map<Project, Set<? extends Project>> cache, Project curr,
161: Project master, Project candidate) {
162: if (!encountered.add(curr)) {
163: return true;
164: }
165: Set<? extends Project> subprojects = cache.get(curr);
166: if (subprojects == null) {
167: SubprojectProvider spp = curr.getLookup().lookup(
168: SubprojectProvider.class);
169: subprojects = spp != null ? spp.getSubprojects()
170: : Collections.<Project> emptySet();
171: cache.put(curr, subprojects);
172: }
173: for (Project child : subprojects) {
174: if (candidate == child) {
175: candidate = null;
176: }
177: if (visit(encountered, cache, child, master, candidate)) {
178: return true;
179: }
180: }
181: if (candidate != null && curr == master) {
182: if (visit(encountered, cache, candidate, master, candidate)) {
183: return true;
184: }
185: }
186: assert encountered.contains(curr);
187: encountered.remove(curr);
188: return false;
189: }
190:
191: private static final class BasicInformation implements
192: ProjectInformation {
193:
194: private final Project p;
195:
196: public BasicInformation(Project p) {
197: this .p = p;
198: }
199:
200: public String getName() {
201: try {
202: return p.getProjectDirectory().getURL()
203: .toExternalForm();
204: } catch (FileStateInvalidException e) {
205: return e.toString();
206: }
207: }
208:
209: public String getDisplayName() {
210: return p.getProjectDirectory().getNameExt();
211: }
212:
213: public Icon getIcon() {
214: return new ImageIcon(
215: Utilities
216: .loadImage("org/netbeans/modules/projectapi/resources/empty.gif")); // NOI18N
217: }
218:
219: public void addPropertyChangeListener(
220: PropertyChangeListener listener) {
221: // never changes
222: }
223:
224: public void removePropertyChangeListener(
225: PropertyChangeListener listener) {
226: // never changes
227: }
228:
229: public Project getProject() {
230: return p;
231: }
232:
233: }
234:
235: }
|