/*
* 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-2008 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.ruby.modules.project.rake;
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.netbeans.api.project.Project;
import org.netbeans.spi.project.ProjectFactory;
import org.netbeans.spi.project.ProjectState;
import org.netbeans.modules.ruby.spi.project.support.rake.RakeBasedProjectType;
import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectHelper;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Singleton {@link ProjectFactory} implementation which handles all Ant-based
* projects by delegating some functionality to registered Ant project types.
* @author Jesse Glick
*/
// to be more eager then Maven project(666). Cf. 151211
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.spi.project.ProjectFactory.class, position=600)
public final class RakeBasedProjectFactorySingleton implements ProjectFactory {
public static final String PROJECT_XML_PATH = "nbproject/project.xml"; // NOI18N
public static final String PROJECT_NS = "http://www.netbeans.org/ns/project/1"; // NOI18N
/** Construct the singleton. */
public RakeBasedProjectFactorySingleton() {}
private static final Map<Project,Reference<RakeProjectHelper>> project2Helper = new WeakHashMap<Project,Reference<RakeProjectHelper>>();
private static final Map<RakeProjectHelper,Reference<Project>> helper2Project = new WeakHashMap<RakeProjectHelper,Reference<Project>>();
private static final Map<RakeBasedProjectType,List<Reference<RakeProjectHelper>>> type2Projects = new HashMap<RakeBasedProjectType,List<Reference<RakeProjectHelper>>>(); //for second part of #42738
private static final Lookup.Result<RakeBasedProjectType> antBasedProjectTypes;
private static Map<String,RakeBasedProjectType> antBasedProjectTypesByType = null;
static {
antBasedProjectTypes = Lookup.getDefault().lookupResult(RakeBasedProjectType.class);
antBasedProjectTypes.addLookupListener(new LookupListener() {
public void resultChanged(LookupEvent ev) {
synchronized (RakeBasedProjectFactorySingleton.class) {
Set<RakeBasedProjectType> oldTypes = type2Projects.keySet();
Set<RakeBasedProjectType> removed = new HashSet<RakeBasedProjectType>(oldTypes);
removed.removeAll(antBasedProjectTypes.allInstances());
antBasedProjectTypesRemoved(removed);
antBasedProjectTypesByType = null;
}
}
});
}
private static void antBasedProjectTypesRemoved(Set<RakeBasedProjectType> removed) {
for (RakeBasedProjectType type : removed) {
List<Reference<RakeProjectHelper>> projects = type2Projects.get(type);
if (projects != null) {
for (Reference<RakeProjectHelper> r : projects) {
RakeProjectHelper helper = r.get();
if (helper != null) {
helper.notifyDeleted();
}
}
}
type2Projects.remove(type);
}
}
private static synchronized RakeBasedProjectType findRakeBasedProjectType(String type) {
if (antBasedProjectTypesByType == null) {
antBasedProjectTypesByType = new HashMap<String,RakeBasedProjectType>();
// No need to synchronize similar calls since this is called only inside
// ProjectManager.mutex. However dkonecny says that allInstances can
// trigger a LookupEvent which would clear antBasedProjectTypesByType,
// so need to initialize that later; and who knows then Lookup changes
// might be fired.
for (RakeBasedProjectType abpt : antBasedProjectTypes.allInstances()) {
antBasedProjectTypesByType.put(abpt.getType(), abpt);
}
}
return antBasedProjectTypesByType.get(type);
}
public boolean isProject(FileObject dir) {
File dirF = FileUtil.toFile(dir);
if (dirF == null) {
return false;
}
// Just check whether project.xml exists. Do not attempt to parse it, etc.
// Do not use FileObject.getFileObject since that may load other sister files.
File projectXmlF = new File(new File(dirF, "nbproject"), "project.xml"); // NOI18N
return projectXmlF.isFile();
}
public Project loadProject(FileObject projectDirectory, ProjectState state) throws IOException {
if (FileUtil.toFile(projectDirectory) == null) {
return null;
}
FileObject projectFile = projectDirectory.getFileObject(PROJECT_XML_PATH);
//#54488: Added check for virtual
if (projectFile == null || !projectFile.isData() || projectFile.isVirtual()) {
return null;
}
File projectDiskFile = FileUtil.toFile(projectFile);
//#63834: if projectFile exists and projectDiskFile does not, do nothing:
if (projectDiskFile == null) {
return null;
}
Document projectXml;
try {
projectXml = XMLUtil.parse(new InputSource(projectDiskFile.toURI().toString()), false, true, XMLUtil.defaultErrorHandler(), null);
} catch (SAXException e) {
IOException ioe = (IOException) new IOException(projectDiskFile + ": " + e.toString()).initCause(e);
Exceptions.attachLocalizedMessage(ioe, NbBundle.getMessage(RakeBasedProjectFactorySingleton.class,
"RakeBasedProjectFactorySingleton.parseError",
projectDiskFile.getAbsolutePath(), e.getMessage()));
throw ioe;
}
Element projectEl = projectXml.getDocumentElement();
if (!"project".equals(projectEl.getLocalName()) || !PROJECT_NS.equals(projectEl.getNamespaceURI())) { // NOI18N
return null;
}
Element typeEl = XMLUtil.findElement(projectEl, "type", PROJECT_NS); // NOI18N
if (typeEl == null) {
return null;
}
String type = XMLUtil.findText(typeEl);
if (type == null) {
return null;
}
RakeBasedProjectType provider = findRakeBasedProjectType(type);
if (provider == null) {
return null;
}
RakeProjectHelper helper = HELPER_CALLBACK.createHelper(projectDirectory, projectXml, state, provider);
Project project = provider.createProject(helper);
project2Helper.put(project, new WeakReference<RakeProjectHelper>(helper));
synchronized (helper2Project) {
helper2Project.put(helper, new WeakReference<Project>(project));
}
List<Reference<RakeProjectHelper>> l = type2Projects.get(provider);
if (l == null) {
type2Projects.put(provider, l = new ArrayList<Reference<RakeProjectHelper>>());
}
l.add(new WeakReference<RakeProjectHelper>(helper));
return project;
}
public void saveProject(Project project) throws IOException, ClassCastException {
Reference<RakeProjectHelper> helperRef = project2Helper.get(project);
if (helperRef == null) {
throw new ClassCastException(project.getClass().getName());
}
RakeProjectHelper helper = helperRef.get();
assert helper != null : "RakeProjectHelper collected for " + project;
HELPER_CALLBACK.save(helper);
}
/**
* Get the project corresponding to a helper.
* For use from {@link RakeProjectHelper}.
* @param helper an Ant project helper object
* @return the corresponding project
*/
public static Project getProjectFor(RakeProjectHelper helper) {
Reference<Project> projectRef;
synchronized (helper2Project) {
projectRef = helper2Project.get(helper);
}
assert projectRef != null : "Expecting a Project reference for " + helper;
Project p = projectRef.get();
assert p != null : "Expecting a non-null Project for " + helper;
return p;
}
/**
* Get the helper corresponding to a project.
* For use from {@link ProjectGenerator}.
* @param project an Ant-based project
* @return the corresponding Ant project helper object, or null if it is unknown
*/
public static RakeProjectHelper getHelperFor(Project p) {
Reference<RakeProjectHelper> helperRef = project2Helper.get(p);
return helperRef != null ? helperRef.get() : null;
}
/**
* Callback to create and access RakeProjectHelper objects from outside its package.
*/
public interface RakeProjectHelperCallback {
RakeProjectHelper createHelper(FileObject dir, Document projectXml, ProjectState state, RakeBasedProjectType type);
void save(RakeProjectHelper helper) throws IOException;
}
/** Defined in RakeProjectHelper's static initializer. */
public static RakeProjectHelperCallback HELPER_CALLBACK;
static {
Class<?> c = RakeProjectHelper.class;
try {
Class.forName(c.getName(), true, c.getClassLoader());
} catch (ClassNotFoundException e) {
assert false : e;
}
assert HELPER_CALLBACK != null;
}
}