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.project.ant;
043:
044: import java.io.File;
045: import java.io.IOException;
046: import java.lang.ref.Reference;
047: import java.lang.ref.WeakReference;
048: import java.util.ArrayList;
049: import java.util.HashMap;
050: import java.util.HashSet;
051: import java.util.List;
052: import java.util.Map;
053: import java.util.Set;
054: import java.util.WeakHashMap;
055: import org.netbeans.api.project.Project;
056: import org.netbeans.spi.project.ProjectFactory;
057: import org.netbeans.spi.project.ProjectState;
058: import org.netbeans.spi.project.support.ant.AntBasedProjectType;
059: import org.netbeans.spi.project.support.ant.AntProjectHelper;
060: import org.openide.filesystems.FileObject;
061: import org.openide.filesystems.FileUtil;
062: import org.openide.util.Exceptions;
063: import org.openide.util.Lookup;
064: import org.openide.util.LookupEvent;
065: import org.openide.util.LookupListener;
066: import org.openide.util.NbBundle;
067: import org.openide.xml.XMLUtil;
068: import org.w3c.dom.Document;
069: import org.w3c.dom.Element;
070: import org.xml.sax.InputSource;
071: import org.xml.sax.SAXException;
072:
073: /**
074: * Singleton {@link ProjectFactory} implementation which handles all Ant-based
075: * projects by delegating some functionality to registered Ant project types.
076: * @author Jesse Glick
077: */
078: public final class AntBasedProjectFactorySingleton implements
079: ProjectFactory {
080:
081: public static final String PROJECT_XML_PATH = "nbproject/project.xml"; // NOI18N
082:
083: public static final String PROJECT_NS = "http://www.netbeans.org/ns/project/1"; // NOI18N
084:
085: /** Construct the singleton. */
086: public AntBasedProjectFactorySingleton() {
087: }
088:
089: private static final Map<Project, Reference<AntProjectHelper>> project2Helper = new WeakHashMap<Project, Reference<AntProjectHelper>>();
090: private static final Map<AntProjectHelper, Reference<Project>> helper2Project = new WeakHashMap<AntProjectHelper, Reference<Project>>();
091: private static final Map<AntBasedProjectType, List<Reference<AntProjectHelper>>> type2Projects = new HashMap<AntBasedProjectType, List<Reference<AntProjectHelper>>>(); //for second part of #42738
092: private static final Lookup.Result<AntBasedProjectType> antBasedProjectTypes;
093: private static Map<String, AntBasedProjectType> antBasedProjectTypesByType = null;
094: static {
095: antBasedProjectTypes = Lookup.getDefault().lookupResult(
096: AntBasedProjectType.class);
097: antBasedProjectTypes.addLookupListener(new LookupListener() {
098: public void resultChanged(LookupEvent ev) {
099: synchronized (AntBasedProjectFactorySingleton.class) {
100: Set<AntBasedProjectType> oldTypes = type2Projects
101: .keySet();
102: Set<AntBasedProjectType> removed = new HashSet<AntBasedProjectType>(
103: oldTypes);
104:
105: removed.removeAll(antBasedProjectTypes
106: .allInstances());
107:
108: antBasedProjectTypesRemoved(removed);
109:
110: antBasedProjectTypesByType = null;
111: }
112: }
113: });
114: }
115:
116: private static void antBasedProjectTypesRemoved(
117: Set<AntBasedProjectType> removed) {
118: for (AntBasedProjectType type : removed) {
119: List<Reference<AntProjectHelper>> projects = type2Projects
120: .get(type);
121: if (projects != null) {
122: for (Reference<AntProjectHelper> r : projects) {
123: AntProjectHelper helper = r.get();
124: if (helper != null) {
125: helper.notifyDeleted();
126: }
127: }
128: }
129: type2Projects.remove(type);
130: }
131: }
132:
133: private static synchronized AntBasedProjectType findAntBasedProjectType(
134: String type) {
135: if (antBasedProjectTypesByType == null) {
136: antBasedProjectTypesByType = new HashMap<String, AntBasedProjectType>();
137: // No need to synchronize similar calls since this is called only inside
138: // ProjectManager.mutex. However dkonecny says that allInstances can
139: // trigger a LookupEvent which would clear antBasedProjectTypesByType,
140: // so need to initialize that later; and who knows then Lookup changes
141: // might be fired.
142: for (AntBasedProjectType abpt : antBasedProjectTypes
143: .allInstances()) {
144: antBasedProjectTypesByType.put(abpt.getType(), abpt);
145: }
146: }
147: return antBasedProjectTypesByType.get(type);
148: }
149:
150: public boolean isProject(FileObject dir) {
151: File dirF = FileUtil.toFile(dir);
152: if (dirF == null) {
153: return false;
154: }
155: // Just check whether project.xml exists. Do not attempt to parse it, etc.
156: // Do not use FileObject.getFileObject since that may load other sister files.
157: File projectXmlF = new File(new File(dirF, "nbproject"),
158: "project.xml"); // NOI18N
159: return projectXmlF.isFile();
160: }
161:
162: public Project loadProject(FileObject projectDirectory,
163: ProjectState state) throws IOException {
164: if (FileUtil.toFile(projectDirectory) == null) {
165: return null;
166: }
167: FileObject projectFile = projectDirectory
168: .getFileObject(PROJECT_XML_PATH);
169: //#54488: Added check for virtual
170: if (projectFile == null || !projectFile.isData()
171: || projectFile.isVirtual()) {
172: return null;
173: }
174: File projectDiskFile = FileUtil.toFile(projectFile);
175: //#63834: if projectFile exists and projectDiskFile does not, do nothing:
176: if (projectDiskFile == null) {
177: return null;
178: }
179: Document projectXml;
180: try {
181: projectXml = XMLUtil.parse(new InputSource(projectDiskFile
182: .toURI().toString()), false, true, Util
183: .defaultErrorHandler(), null);
184: } catch (SAXException e) {
185: IOException ioe = (IOException) new IOException(
186: projectDiskFile + ": " + e.toString()).initCause(e);
187: Exceptions.attachLocalizedMessage(ioe, NbBundle.getMessage(
188: AntBasedProjectFactorySingleton.class,
189: "AntBasedProjectFactorySingleton.parseError",
190: projectDiskFile.getAbsolutePath(), e.getMessage()));
191: throw ioe;
192: }
193: Element projectEl = projectXml.getDocumentElement();
194: if (!"project".equals(projectEl.getLocalName())
195: || !PROJECT_NS.equals(projectEl.getNamespaceURI())) { // NOI18N
196: return null;
197: }
198: Element typeEl = Util
199: .findElement(projectEl, "type", PROJECT_NS); // NOI18N
200: if (typeEl == null) {
201: return null;
202: }
203: String type = Util.findText(typeEl);
204: if (type == null) {
205: return null;
206: }
207: AntBasedProjectType provider = findAntBasedProjectType(type);
208: if (provider == null) {
209: return null;
210: }
211: AntProjectHelper helper = HELPER_CALLBACK.createHelper(
212: projectDirectory, projectXml, state, provider);
213: Project project = provider.createProject(helper);
214: project2Helper.put(project,
215: new WeakReference<AntProjectHelper>(helper));
216: synchronized (helper2Project) {
217: helper2Project.put(helper, new WeakReference<Project>(
218: project));
219: }
220: List<Reference<AntProjectHelper>> l = type2Projects
221: .get(provider);
222:
223: if (l == null) {
224: type2Projects.put(provider,
225: l = new ArrayList<Reference<AntProjectHelper>>());
226: }
227:
228: l.add(new WeakReference<AntProjectHelper>(helper));
229:
230: return project;
231: }
232:
233: public void saveProject(Project project) throws IOException,
234: ClassCastException {
235: Reference<AntProjectHelper> helperRef = project2Helper
236: .get(project);
237: if (helperRef == null) {
238: throw new ClassCastException(project.getClass().getName());
239: }
240: AntProjectHelper helper = helperRef.get();
241: assert helper != null : "AntProjectHelper collected for "
242: + project;
243: HELPER_CALLBACK.save(helper);
244: }
245:
246: /**
247: * Get the project corresponding to a helper.
248: * For use from {@link AntProjectHelper}.
249: * @param helper an Ant project helper object
250: * @return the corresponding project
251: */
252: public static Project getProjectFor(AntProjectHelper helper) {
253: Reference<Project> projectRef;
254: synchronized (helper2Project) {
255: projectRef = helper2Project.get(helper);
256: }
257: assert projectRef != null : "Expecting a Project reference for "
258: + helper;
259: Project p = projectRef.get();
260: assert p != null : "Expecting a non-null Project for " + helper;
261: return p;
262: }
263:
264: /**
265: * Get the helper corresponding to a project.
266: * For use from {@link ProjectGenerator}.
267: * @param project an Ant-based project
268: * @return the corresponding Ant project helper object, or null if it is unknown
269: */
270: public static AntProjectHelper getHelperFor(Project p) {
271: Reference<AntProjectHelper> helperRef = project2Helper.get(p);
272: return helperRef != null ? helperRef.get() : null;
273: }
274:
275: /**
276: * Callback to create and access AntProjectHelper objects from outside its package.
277: */
278: public interface AntProjectHelperCallback {
279: AntProjectHelper createHelper(FileObject dir,
280: Document projectXml, ProjectState state,
281: AntBasedProjectType type);
282:
283: void save(AntProjectHelper helper) throws IOException;
284: }
285:
286: /** Defined in AntProjectHelper's static initializer. */
287: public static AntProjectHelperCallback HELPER_CALLBACK;
288: static {
289: Class<?> c = AntProjectHelper.class;
290: try {
291: Class.forName(c.getName(), true, c.getClassLoader());
292: } catch (ClassNotFoundException e) {
293: assert false : e;
294: }
295: assert HELPER_CALLBACK != null;
296: }
297:
298: }
|