package org.netbeans.gradle.project;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.gradle.project.model.NbGenericModelInfo;
import org.netbeans.gradle.project.script.CommonScripts;
import org.netbeans.gradle.project.script.DefaultScriptFileProvider;
import org.netbeans.gradle.project.script.ScriptFileProvider;
import org.netbeans.gradle.project.util.NbFileUtils;
import org.netbeans.spi.project.ProjectFactory;
import org.netbeans.spi.project.ProjectFactory2;
import org.netbeans.spi.project.ProjectState;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
@org.openide.util.lookup.ServiceProvider(service = ProjectFactory.class)
public class NbGradleProjectFactory implements ProjectFactory2 {
private static final Logger LOGGER = Logger.getLogger(NbGradleProjectFactory.class.getName());
static final RootProjectRegistry ROOT_PROJECT_REGISTRY = new RootProjectRegistry();
static final GlobalSettingsFileManager SETTINGS_FILE_MANAGER
= new DefaultGlobalSettingsFileManager(ROOT_PROJECT_REGISTRY);
public static final ScriptFileProvider DEFAULT_SCRIPT_FILE_PROVIDER = new DefaultScriptFileProvider();
private static final ConcurrentMap<Path, RefCounter> SAFE_TO_OPEN_PROJECTS
= new ConcurrentHashMap<>();
private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
public static Project loadSafeProject(Path projectDir) throws IOException {
return loadSafeProject(projectDir.toFile());
}
public static Project loadSafeProject(File projectDir) throws IOException {
FileObject projectDirObj = FileUtil.toFileObject(projectDir);
if (projectDirObj == null) {
throw new IllegalArgumentException("Project directory does not exist: " + projectDir);
}
return loadSafeProject(projectDirObj);
}
public static Project loadSafeProject(FileObject projectDir) throws IOException {
Project result = findSafeProject(projectDir);
if (result == null) {
throw new IllegalArgumentException("Project does not exist: " + projectDir);
}
return result;
}
public static NbGradleProject tryGetGradleProject(Project project) {
return project != null
? project.getLookup().lookup(NbGradleProject.class)
: null;
}
public static NbGradleProject getGradleProject(Project project) {
NbGradleProject gradleProject = tryGetGradleProject(project);
if (gradleProject == null) {
throw new IllegalArgumentException("Not a Gradle project: " + project.getProjectDirectory());
}
return gradleProject;
}
public static NbGradleProject tryLoadSafeGradleProject(Path projectDir) {
return tryGetGradleProject(tryLoadSafeProject(projectDir));
}
public static NbGradleProject tryLoadSafeGradleProject(File projectDir) {
return tryGetGradleProject(tryLoadSafeProject(projectDir));
}
public static Project tryLoadSafeProject(Path projectDir) {
return tryLoadSafeProject(projectDir.toFile());
}
public static Project tryLoadSafeProject(File projectDir) {
ExceptionHelper.checkNotNullArgument(projectDir, "projectDir");
FileObject projectDirObj = FileUtil.toFileObject(projectDir);
if (projectDirObj == null) {
return null;
}
return tryLoadSafeProject(projectDirObj);
}
public static Project tryLoadSafeProject(FileObject projectDir) {
try {
return findSafeProject(projectDir);
} catch (IOException | IllegalArgumentException ex) {
LOGGER.log(Level.INFO, "Failed to load project: " + projectDir, ex);
return null;
}
}
private static Project findSafeProject(FileObject projectDir) throws IOException {
ExceptionHelper.checkNotNullArgument(projectDir, "projectDir");
try (Closeable safeToOpen = NbGradleProjectFactory.safeToOpen(projectDir)) {
assert safeToOpen != null; // Avoid warning
return ProjectManager.getDefault().findProject(projectDir);
}
}
public static Closeable safeToOpen(FileObject projectDir) {
File projectDirFile = FileUtil.toFile(projectDir);
if (projectDirFile == null) {
return DummyCloseable.INSTANCE;
}
return safeToOpen(projectDirFile);
}
public static Closeable safeToOpen(File projectDir) {
return safeToOpen(projectDir.toPath());
}
public static Closeable safeToOpen(Path projectDir) {
ExceptionHelper.checkNotNullArgument(projectDir, "projectDir");
Path normProjectDir = projectDir.normalize();
RefCounter counter;
RefCounter newCounter;
boolean set;
do {
counter = SAFE_TO_OPEN_PROJECTS.get(normProjectDir);
if (counter == null) {
newCounter = new RefCounter(1);
set = SAFE_TO_OPEN_PROJECTS.putIfAbsent(normProjectDir, newCounter) == null;
}
else {
newCounter = counter.increment();
set = SAFE_TO_OPEN_PROJECTS.replace(normProjectDir, counter, newCounter);
}
} while(!set);
if (counter == null) {
LOGGER.log(Level.INFO, "Project is now safe to load: {0}", projectDir);
}
return new CounterCloser(normProjectDir);
}
public static boolean isSafeToOpen(FileObject projectDir) {
Path projectDirPath = NbFileUtils.asPath(FileUtil.toFile(projectDir));
if (projectDirPath == null) {
return false;
}
return SAFE_TO_OPEN_PROJECTS.containsKey(projectDirPath);
}
private static boolean hasBuildFile(FileObject directory) {
Path dirAsPath = NbFileUtils.asPath(directory);
return dirAsPath != null
? NbGenericModelInfo.tryGuessBuildFilePath(dirAsPath, DEFAULT_SCRIPT_FILE_PROVIDER) != null
: false;
}
@Override
public ProjectManager.Result isProject2(FileObject projectDirectory) {
if (isProject(projectDirectory)) {
return new ProjectManager.Result(NbIcons.getGradleIconAsIcon());
}
else {
return null;
}
}
@Override
public boolean isProject(FileObject projectDirectory) {
if (isSafeToOpen(projectDirectory)) {
return true;
}
// We will not load projects from the temporary directory simply
// because NetBeans has a habit to put temporary gradle files to
// them and then tries to load it which will fail because NetBeans will
// delete them soon.
if (TEMP_DIR != null) {
File tempDir = FileUtil.normalizeFile(new File(TEMP_DIR));
FileObject tempDirObj = FileUtil.toFileObject(tempDir);
if (tempDirObj != null) {
if (FileUtil.getRelativePath(tempDirObj, projectDirectory) != null) {
return false;
}
}
}
if (hasBuildFile(projectDirectory)) {
return true;
}
if (projectDirectory.getNameExt().equalsIgnoreCase(CommonScripts.BUILD_SRC_NAME)) {
FileObject parent = projectDirectory.getParent();
if (parent != null) {
if (hasBuildFile(parent)) {
return true;
}
}
}
return false;
}
@Override
public Project loadProject(FileObject dir, ProjectState state) throws IOException {
// Note: Netbeans might call this method without calling isProject
// first on directories within the project. If this method throws
// an exception in this case, NetBeans will fail to check for the class
// path of files. And finding the cause of such behaviour is extremly
// hard if this is not known.
if (!isProject(dir)) {
return null;
}
return NbGradleProject.createProject(dir, state);
}
@Override
public void saveProject(final Project project) throws IOException {
}
private enum DummyCloseable implements Closeable {
INSTANCE;
@Override
public void close() {
}
}
private static class RefCounter {
private final long refCount;
public RefCounter(long value) {
this.refCount = value;
}
public long getRefCount() {
return refCount;
}
public RefCounter increment() {
return new RefCounter(refCount + 1);
}
public RefCounter decrement() {
return new RefCounter(refCount - 1);
}
}
private static class CounterCloser implements Closeable {
private final Path key;
private final AtomicBoolean closed;
public CounterCloser(Path key) {
ExceptionHelper.checkNotNullArgument(key, "key");
this.key = key;
this.closed = new AtomicBoolean(false);
}
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
RefCounter counter;
RefCounter newCounter;
boolean set;
do {
counter = SAFE_TO_OPEN_PROJECTS.get(key);
newCounter = counter.decrement();
if (newCounter.getRefCount() == 0) {
set = SAFE_TO_OPEN_PROJECTS.remove(key, counter);
}
else {
set = SAFE_TO_OPEN_PROJECTS.replace(key, counter, newCounter);
}
} while (!set);
if (newCounter.getRefCount() == 0) {
LOGGER.log(Level.INFO, "Project is not safe to load anymore: {0}", key);
}
}
}
}
}