/*
* Project Info: http://jcae.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This program 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* (C) Copyright 2005-2010, by EADS France
*/
package org.jcae.netbeans.mesh;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.jcae.mesh.JCAEFormatter;
import org.jcae.netbeans.options.OptionNode;
import org.jcae.netbeans.viewer3d.ViewManager;
import org.jcae.vtk.CameraManager;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.filesystems.FileUtil;
import org.openide.modules.InstalledFileLocator;
import org.openide.nodes.Node;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.actions.CookieAction;
import org.openide.windows.IOProvider;
import org.openide.windows.InputOutput;
import org.openide.windows.OutputEvent;
import org.openide.windows.OutputListener;
import org.openide.windows.OutputWriter;
import org.python.util.PythonInterpreter;
/**
*
* @author Jerome Robert
*/
public abstract class AlgoAction extends CookieAction {
protected static class Redirector extends Thread {
private static final Pattern PATTERN;
static
{
String floatRegex = "([-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?)";
String separator = "[\\s,]+";
PATTERN = Pattern.compile(floatRegex + separator + floatRegex + separator + floatRegex);
}
private final BufferedReader in;
private final OutputWriter out;
private final CameraManager cameraManager;
public Redirector(InputStream in, OutputWriter out) {
this.in = new BufferedReader(new InputStreamReader(in));
this.out = out;
this.cameraManager = ViewManager.getDefault().getCurrentView().getCameraManager();
}
@Override
public void run() {
try {
String line = in.readLine();
while (line != null) {
Matcher m = PATTERN.matcher(line);
if(m.find())
{
final float x = Float.parseFloat(m.group(1));
final float y = Float.parseFloat(m.group(2));
final float z = Float.parseFloat(m.group(3));
out.println(line, new OutputListener() {
public void outputLineSelected(OutputEvent ev) {}
public void outputLineCleared(OutputEvent ev) {}
public void outputLineAction(OutputEvent ev) {
cameraManager.zoomTo(x, y, z, 20);
}
});
}
else
out.println(line);
line = in.readLine();
}
} catch (IOException ex) {
//the child process has been killed
out.println("End of stream");
}
}
}
@Override
protected int mode() {
return CookieAction.MODE_ALL;
}
private String join(List<String> l)
{
StringBuilder sb = new StringBuilder();
for(String s:l)
{
sb.append(s);
sb.append(' ');
}
return sb.toString();
}
private void runProcess(ProcessBuilder pb, final InputOutput io) throws IOException {
io.getOut().println("Running "+join(pb.command()));
final Process p = pb.start();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
io.select();
}
});
new Redirector(p.getInputStream(), io.getOut()).start();
new Redirector(p.getErrorStream(), io.getErr()).start();
final ProgressHandle ph = ProgressHandleFactory.createHandle(getName(),
new Cancellable() {
@Override
public boolean cancel() {
p.destroy();
return true;
}
});
ph.start();
try {
p.waitFor();
if(p.exitValue() != 0)
throw new IOException("The process returned "+p.exitValue());
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
} finally {
ph.finish();
}
}
/** Check that groups are all from the same mesh */
private boolean checkGroups(Node[] nodes)
{
HashSet<AmibeDataObject> ados = new HashSet<AmibeDataObject>();
for(Node n:nodes)
ados.add(amibeDataObject(n));
if(ados.size() != 1)
{
JOptionPane.showMessageDialog(null, "Selected groups must belong to the same mesh.");
return false;
}
else
return true;
}
private String groupNames(Node[] nodes, AmibeDataObject ado)
{
StringBuilder sb = new StringBuilder(2*nodes.length);
boolean first = true;
assert ado != null;
if(ado.getGroups() == null)
return "";
assert ado.getGroups().getGroups() != null;
int allGroupsNumber = ado.getGroups().getGroups().length;
TreeSet<String> selectedGroups = new TreeSet<String>();
for(Node n:nodes)
{
GroupNode gn = n.getLookup().lookup(GroupNode.class);
if(gn != null)
selectedGroups.add(gn.getName());
}
if(selectedGroups.size() == allGroupsNumber)
return "";
for(String s:selectedGroups)
{
if(!first)
sb.append(",");
sb.append(s);
first = false;
}
return sb.toString();
}
private AmibeDataObject amibeDataObject(Node node)
{
AmibeDataObject ado = node.getLookup().lookup(AmibeDataObject.class);
if(ado == null)
{
ado = node.getParentNode().getParentNode().getLookup().lookup(AmibeDataObject.class);
}
return ado;
}
@Override
protected void performAction(Node[] activatedNodes) {
if(checkGroups(activatedNodes))
{
AmibeDataObject ado = amibeDataObject(activatedNodes[0]);
String groupNames = groupNames(activatedNodes, ado);
List<String> args = getArguments(ado);
if (args != null) {
try
{
String command;
if(!groupNames.isEmpty())
{
args.remove(args.size()-1); //inputdir
args.remove(args.size()-1); //outputdir
args.add(0,"--immutable-border");
args.add(0, getCommand());
args.add(0, groupNames);
args.add(0, ado.getMeshDirectory());
args.add(0, ado.getMeshDirectory());
command="submesh";
}
else
command=getCommand();
File pyFile = InstalledFileLocator.getDefault().locate(
"amibe-python/" + command + ".py",
"org.jcae.netbeans", false);
InputOutput io = IOProvider.getDefault().getIO(getName(), true);
if ((Boolean) OptionNode.SAME_JVM.getValue())
runInSameVM(args, pyFile, io);
else
runInOtherVM(activatedNodes[0], args, pyFile, io);
postProcess(ado);
ado.refreshGroups();
}
catch(IOException ex)
{
Exceptions.printStackTrace(ex);
}
}
}
}
protected void postProcess(AmibeDataObject ado)
{
}
private void runInSameVM(List<String> args, File pyFile, final InputOutput io) {
final ProgressHandle ph = ProgressHandleFactory.createHandle(getName());
Logger root = Logger.getLogger("org.jcae.mesh");
final JCAEFormatter jcaeFormatter = new JCAEFormatter();
//getting and redirecting logs from Bora mesher
root.setLevel(Level.INFO);
Handler redirector = new Handler() {
@Override
public void publish(LogRecord record) {
io.getOut().print(getFormatter().format(record));
}
@Override
public void close() throws SecurityException {
}
@Override
public void flush() {
}
};
try
{
root.addHandler(redirector);
for (Handler h : root.getHandlers()) {
h.setFormatter(jcaeFormatter);
}
PythonInterpreter interp = new PythonInterpreter();
interp.setOut(io.getOut());
interp.setIn(io.getIn());
interp.setErr(io.getErr());
interp.set("args", args);
interp.exec("import sys");
interp.exec("sys.argv.extend(args)");
ph.start();
interp.execfile(pyFile.getPath());
}
finally
{
root.removeHandler(redirector);
ph.finish();
}
}
protected String getClassPath()
{
File amibe = InstalledFileLocator.getDefault().locate(
"modules/ext/amibe.jar", "org.jcae.netbeans", false);
File trove = InstalledFileLocator.getDefault().locate(
"modules/ext/trove.jar", "org.jcae.netbeans", false);
File jython = InstalledFileLocator.getDefault().locate(
"modules/ext/jython.jar", "org.jcae.netbeans", false);
return amibe.getPath() + File.pathSeparatorChar + trove.getPath() +
File.pathSeparatorChar + jython.getPath();
}
private void runInOtherVM(Node node, List<String> args, File pyFile, InputOutput io)
throws IOException
{
ProcessBuilder pb = new ProcessBuilder();
pb.command().add(System.getProperty("java.home") + File.separatorChar +
"bin" + File.separatorChar + "java");
for (String s:OptionNode.getJVMOptions())
pb.command().add(s);
String home = System.getProperty("netbeans.user");
File dir = new File(new File(new File(new File(home), "var"), "cache"), "jython");
pb.command().add("-Dpython.cachedir="+dir.getPath());
//Required to get wildcard import
pb.command().add("-Dpython.cachedir.skip=false");
pb.command().add("-cp");
pb.command().add(getClassPath());
pb.command().add("org.python.util.jython");
pb.command().add(pyFile.getPath());
pb.command().addAll(args);
customizeProcessBuilder(node, pb);
runProcess(pb, io);
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
protected abstract String getCommand();
/** Show a dialog and return a list of argument or null to cancel */
protected abstract List<String> getArguments(AmibeDataObject node);
@Override
protected boolean asynchronous() {
return true;
}
protected void customizeProcessBuilder(Node node, ProcessBuilder pb) {
AmibeNode n = node.getLookup().lookup(AmibeNode.class);
if( n != null )
{
File f = FileUtil.toFile(n.getDataObject().getPrimaryFile().getParent());
pb.directory(f);
}
}
@Override
protected Class<?>[] cookieClasses() {
return new Class<?>[] { AmibeDataObject.class, GroupNode.class };
}
}