package ring.commands;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.python.util.PythonInterpreter;
import ring.deployer.DeployedMUDFactory;
import ring.python.Interpreter;
/**
* Package-level indexer class that indexes Jython script files and turns them
* into RingMUD Command objects. JythonIndexer takes the following properties as parameters:<br/>
* directory: The directory to index.
* @author projectmoon
*
*/
class JythonIndexer implements CommandIndexer {
private String filePath;
/**
* A list of already-executed files. Should speed things up a bit
* if we don't need to execute the same file twice (or more).
*/
private final ArrayList<String> fileCache = new ArrayList<String>();
private final ArrayList<Command> cmds = new ArrayList<Command>();
private boolean indexed = false;
private static PythonInterpreter INTERP = Interpreter.INSTANCE.getInterpreter();
private static final Pattern JYTHON_PATTERN = Pattern.compile("^class\\s+(\\w+)\\s*\\([\\w,_\\s]*Command[\\w,_\\s]*\\):$");
public JythonIndexer() {}
/**
* Indexes commands from Jython script files.
*/
public void index() throws IllegalStateException {
filePath = DeployedMUDFactory.currentMUD().getLocation() + File.separator + "commands/";
initInterpreter();
File dir = new File(filePath);
if (dir.isDirectory()) {
File[] pythonFiles = dir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.getAbsolutePath().endsWith(".py");
}
});
//Give the cache plenty of room for the files. Saves time later.
fileCache.ensureCapacity(pythonFiles.length);
//Process each file and attempt to instantiate classes out of them.
for (File file : pythonFiles) {
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = "";
while ((line = reader.readLine()) != null) {
String className = findClassName(line);
if (className != null) {
instantiateCommand(file.getAbsolutePath().toString(), className);
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println(" [JythonIndexer]: Indexed " + cmds.size() + " commands.");
indexed = true;
}
/**
* Initializes the Jython environment for use with the indexer.
*/
private void initInterpreter() {
INTERP.exec("from ring.commands import Command");
INTERP.exec("from ring.commands import CommandSender");
INTERP.exec("from ring.commands import CommandParameters");
}
/**
* Extracts a Command object from the given Python file.
* @param filename The script to pull the command from.
* @param name The name of the class to create.
*/
private void instantiateCommand(String filename, String name) {
//Load the python file if it hasn't already been.
attemptExecute(filename);
//Create a new Command object in the interpreter and extract it.
INTERP.exec("_ring_obj = " + name + "()");
Command cmd = (Command)INTERP.get("_ring_obj", Command.class);
if (cmd == null) {
System.out.println(this + ": Error: There was an error creating command " + name);
}
else if (cmd.getCommandName() == null) {
System.err.println(this + ": Error: Command object " + name + " does not define getCommandName().");
}
else {
cmds.add(cmd);
}
}
/**
* Executes a python script file if it's not in the cache and has not already
* been executed. This stops the indexer from repeatedly executing one file that
* may have more than one command in it, and reduces overhead.
* @param filename
*/
private void attemptExecute(String filename) {
if (fileCache.contains(filename) == false) {
INTERP.execfile(filename);
fileCache.add(filename);
}
}
/**
* Responsible for pulling out Python class declarations that implement the Command
* interface.
* @param python The line of python script to match against.
* @return The name of the class, if found. Otherwise, null is returned.
*/
private String findClassName(String python) {
Matcher m = JYTHON_PATTERN.matcher(python);
if (m.matches()) {
return m.group(1);
}
else {
return null;
}
}
/**
* Gets the list of indexed commands. If index() has not already been called,
* this method calls it automatically.
*/
public List<Command> getCommands() throws IllegalStateException {
if (!indexed) {
index();
}
return cmds;
}
/**
* Returns "[JythonIndexer]".
*/
public String toString() {
return "[JythonIndexer]";
}
@Override
public Properties getProperties() {
throw new UnsupportedOperationException();
}
@Override
public void setProperties(Properties props) {
throw new UnsupportedOperationException();
}
}