/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2016 Neil C Smith.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 3 for more details.
*
* You should have received a copy of the GNU General Public License version 3
* along with this work; if not, see http://www.gnu.org/licenses/
*
*
* Please visit http://neilcsmith.net if you need additional information or
* have any questions.
*/
package net.neilcsmith.praxis.live.project;
import java.beans.PropertyChangeEvent;
import net.neilcsmith.praxis.live.project.ui.PraxisCustomizerProvider;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.swing.Icon;
import net.neilcsmith.praxis.core.CallArguments;
import net.neilcsmith.praxis.live.core.api.Callback;
import net.neilcsmith.praxis.live.project.api.ExecutionLevel;
import net.neilcsmith.praxis.live.project.api.FileHandler;
import net.neilcsmith.praxis.live.project.api.PraxisProject;
import net.neilcsmith.praxis.live.project.api.PraxisProjectProperties;
import net.neilcsmith.praxis.live.project.ui.PraxisLogicalViewProvider;
import net.neilcsmith.praxis.live.project.ui.ProjectDialogManager;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.ProjectState;
import org.netbeans.spi.project.support.LookupProviderSupport;
import org.netbeans.spi.project.ui.PrivilegedTemplates;
import org.netbeans.spi.project.ui.support.UILookupMergerSupport;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;
/**
*
* @author Neil C Smith (http://neilcsmith.net)
*/
public class DefaultPraxisProject extends PraxisProject {
private final static RequestProcessor RP = new RequestProcessor(PraxisProject.class);
private final static FileObject[] EMPTY_FILES = new FileObject[0];
private final FileObject directory;
private final FileObject projectFile;
private final PraxisProjectProperties properties;
private final Lookup lookup;
private final HelperListener helperListener;
private final PropertiesListener propsListener;
private final ProjectState state;
private final Set<FileObject> executedBuildFiles = new HashSet<>();
private boolean actionsEnabled = true;
DefaultPraxisProject(FileObject directory, FileObject projectFile, ProjectState state)
throws IOException {
this.directory = directory;
this.projectFile = projectFile;
this.state = state;
properties = parseProjectFile(projectFile);
propsListener = new PropertiesListener();
properties.addPropertyChangeListener(propsListener);
Lookup base = Lookups.fixed(new Object[]{
this,
properties,
new Info(),
new ActionImpl(),
state,
new PraxisCustomizerProvider(this),
new PraxisLogicalViewProvider(this),
new BaseTemplates(),
UILookupMergerSupport.createPrivilegedTemplatesMerger()
});
this.lookup = LookupProviderSupport.createCompositeLookup(base, LOOKUP_PATH);
helperListener = new HelperListener();
ProjectHelper.getDefault().addPropertyChangeListener(
WeakListeners.propertyChange(helperListener, ProjectHelper.getDefault()));
}
@Override
public FileObject getProjectDirectory() {
return directory;
}
@Override
public Lookup getLookup() {
return lookup;
}
private ProjectPropertiesImpl parseProjectFile(FileObject projectFile) {
ProjectPropertiesImpl props = new ProjectPropertiesImpl(this);
try {
PXPReader.initializeProjectProperties(directory, projectFile, props);
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
return props;
}
public void save() throws IOException {
PXPWriter.writeProjectProperties(directory, projectFile, properties);
}
private void invokeBuild() {
actionsEnabled = false;
executedBuildFiles.clear();
FileObject[] buildFiles = properties.getProjectFiles(ExecutionLevel.BUILD);
FileHandlerIterator itr = new FileHandlerIterator(buildFiles, EMPTY_FILES);
itr.start();
}
private void invokeRun() {
actionsEnabled = false;
FileObject[] buildFiles = properties.getProjectFiles(ExecutionLevel.BUILD);
if (!executedBuildFiles.isEmpty()) {
Set<FileObject> files = new HashSet<FileObject>(Arrays.asList(buildFiles));
files.removeAll(executedBuildFiles);
buildFiles = files.toArray(EMPTY_FILES);
}
FileObject[] runFiles = properties.getProjectFiles(ExecutionLevel.RUN);
FileHandlerIterator itr = new FileHandlerIterator(buildFiles, runFiles);
itr.start();
}
private class Info implements ProjectInformation {
@Override
public String getName() {
return directory.getName();
}
@Override
public String getDisplayName() {
return directory.getName();
}
@Override
public Icon getIcon() {
return ImageUtilities.loadImageIcon("net/neilcsmith/praxis/live/project/resources/pxp16.png", false);
}
@Override
public Project getProject() {
return DefaultPraxisProject.this;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
// no op
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
// no op
}
}
private class BaseTemplates implements PrivilegedTemplates {
@Override
public String[] getPrivilegedTemplates() {
return new String[]{
"Templates/Other/Folder",
"Templates/Other/org-netbeans-modules-project-ui-NewFileIterator-folderIterator"
};
}
}
private class HelperListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (ProjectHelper.PROP_HUB_CONNECTED.equals(evt.getPropertyName())) {
if (ProjectHelper.getDefault().isConnected()) {
actionsEnabled = true;
executedBuildFiles.clear();
}
}
}
}
private class PropertiesListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
state.markModified();
RP.schedule(new Runnable() {
@Override
public void run() {
try {
ProjectManager.getDefault().saveProject(DefaultPraxisProject.this);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}, 500, TimeUnit.MILLISECONDS);
}
}
private class ActionImpl implements ActionProvider {
@Override
public String[] getSupportedActions() {
return new String[]{
ActionProvider.COMMAND_RUN,
ActionProvider.COMMAND_BUILD
};
}
@Override
public void invokeAction(String command, Lookup context) throws IllegalArgumentException {
if (ActionProvider.COMMAND_RUN.equals(command)) {
invokeRun();
} else if (ActionProvider.COMMAND_BUILD.equals(command)) {
invokeBuild();
}
}
@Override
public boolean isActionEnabled(String command, Lookup context) throws IllegalArgumentException {
return ProjectHelper.getDefault().isConnected() && actionsEnabled;
}
}
class FileHandlerIterator implements Cancellable {
private FileObject[] buildFiles;
private FileObject[] runFiles;
private ProgressHandle progress = null;
private int index = -1;
private FileHandler.Provider[] handlers = new FileHandler.Provider[0];
private Map<FileObject, List<String>> warnings;
private ExecutionLevel level;
private FileHandlerIterator(FileObject[] buildFiles, FileObject[] runFiles) {
this.buildFiles = buildFiles;
this.runFiles = runFiles;
handlers = Lookup.getDefault().lookupAll(FileHandler.Provider.class).toArray(handlers);
}
public void start() {
int totalFiles = buildFiles.length + runFiles.length;
if (totalFiles == 0) {
return;
}
progress = ProgressHandleFactory.createHandle("Executing...", this);
progress.setInitialDelay(0);
progress.start(totalFiles);
next();
}
@Override
public boolean cancel() {
return false;
}
private void next() {
index++;
if (index >= (buildFiles.length + runFiles.length)) {
done();
return;
}
FileObject file;
// ExecutionLevel level;
if (index < buildFiles.length) {
file = buildFiles[index];
executedBuildFiles.add(file);
level = ExecutionLevel.BUILD;
} else {
file = runFiles[index - buildFiles.length];
level = ExecutionLevel.RUN;
}
FileHandler handler = findHandler(level, file);
String msg = FileUtil.getRelativePath(getProjectDirectory(), file) + " [" + level + "]";
progress.progress(msg, index);
try {
handler.process(new CallbackImpl(handler, file));
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
if (continueOnError(handler, file, null)) {
next();
} else {
done();
}
}
}
private boolean continueOnError(FileHandler handler, FileObject file, CallArguments args) {
return ProjectDialogManager.getDefault().continueOnError(
DefaultPraxisProject.this, file, args, level);
}
private void logWarnings(FileHandler handler, FileObject file) {
List<String> wl = handler.getWarnings();
if (wl == null || wl.isEmpty()) {
return;
}
if (warnings == null) {
warnings = new LinkedHashMap<FileObject, List<String>>();
}
warnings.put(file, wl);
}
private void done() {
progress.finish();
if (warnings != null) {
ProjectDialogManager.getDefault().showWarningsDialog(DefaultPraxisProject.this, warnings, level);
}
actionsEnabled = true;
}
private FileHandler findHandler(ExecutionLevel level, FileObject file) {
FileHandler handler = null;
for (FileHandler.Provider provider : handlers) {
try {
handler = provider.getHandler(DefaultPraxisProject.this, level, file);
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
if (handler != null) {
break;
}
}
if (handler == null) {
handler = new DefaultFileHandler(DefaultPraxisProject.this, level, file);
}
return handler;
}
private class CallbackImpl implements Callback {
private FileHandler handler;
private FileObject file;
private CallbackImpl(FileHandler handler, FileObject file) {
this.handler = handler;
this.file = file;
}
@Override
public void onReturn(CallArguments args) {
logWarnings(handler, file);
next();
}
@Override
public void onError(CallArguments args) {
if (continueOnError(handler, file, args)) {
next();
} else {
done();
}
}
}
}
}