/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library 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 library 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 library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.emu;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
import javax.naming.NamingException;
import org.jnode.emu.naming.BasicNameSpace;
import org.jnode.emu.plugin.model.DummyExtensionPoint;
import org.jnode.naming.InitialNaming;
import org.jnode.nanoxml.XMLElement;
import org.jnode.shell.ShellManager;
import org.jnode.shell.alias.AliasManager;
import org.jnode.shell.alias.def.DefaultAliasManager;
import org.jnode.shell.def.DefaultShellManager;
import org.jnode.shell.help.HelpFactory;
import org.jnode.shell.help.def.DefaultHelpFactory;
import org.jnode.shell.syntax.DefaultSyntaxManager;
import org.jnode.shell.syntax.SyntaxBundle;
import org.jnode.shell.syntax.SyntaxManager;
import org.jnode.shell.syntax.SyntaxSpecAdapter;
import org.jnode.shell.syntax.SyntaxSpecLoader;
import org.jnode.shell.syntax.XMLSyntaxSpecAdapter;
/**
* Emu is the core of a light-weight JNode emulator that allows (some) JNode
* applications to be run using a classic JVM in the context of a JNode development sandbox.
* <p>
* An Emu is also a large flightless bird ... which seems kind of appropriate.
*
* @author Levente S\u00e1ntha
* @author crawley@jnode.org
*/
public class Emu {
// FIXME configuring a hard-coded list of projects is a bad idea.
private static final String[] ALL_PROJECTS = new String[]{
"cli", "core", "distr", "fs", "gui", "net", "shell", "sound", "textui"
};
// FIXME configuring a hard-coded list of command plugins is a bad idea.
private static final String[] DEFAULT_PLUGIN_IDS = new String[] {
"org.jnode.command.archive",
"org.jnode.command.common",
"org.jnode.command.dev.ant",
"org.jnode.command.dev",
"org.jnode.command.file",
"org.jnode.command.net",
"org.jnode.command.system",
"org.jnode.command.util",
"org.jnode.shell.command.driver.console",
"org.jnode.apps.editor",
"org.jnode.apps.edit",
"org.jnode.apps.commander",
"org.jnode.apps.console",
};
private final File root;
private final AliasManager aliasMgr;
private final SyntaxManager syntaxMgr;
private final HashSet<String> configuredPlugins = new HashSet<String>();
public Emu(File root) throws EmuException {
this(root, DEFAULT_PLUGIN_IDS);
}
/**
* The constructor initializes a minimal subset of JNode services to allow us to run JNode commands.
*
* @param root the notional JNode sandbox root directory or <code>null</code>.
* @throws EmuException
*/
public Emu(File root, String[] pluginNames) throws EmuException {
if (root == null) {
root = new File("").getAbsoluteFile();
System.err.println("Assuming that the JNode root is '" + root + "'");
}
this.root = root;
InitialNaming.setNameSpace(new BasicNameSpace());
try {
InitialNaming.bind(DeviceManager.NAME, DeviceManager.INSTANCE);
aliasMgr = new DefaultAliasManager(new DummyExtensionPoint()).createAliasManager();
syntaxMgr = new DefaultSyntaxManager(new DummyExtensionPoint()).createSyntaxManager();
for (String pluginName : pluginNames) {
configurePlugin(pluginName);
}
System.setProperty("jnode.invoker", "thread");
System.setProperty("jnode.interpreter", "redirecting");
System.setProperty("jnode.debug", "true");
InitialNaming.bind(AliasManager.NAME, aliasMgr);
InitialNaming.bind(ShellManager.NAME, new DefaultShellManager());
InitialNaming.bind(SyntaxManager.NAME, syntaxMgr);
InitialNaming.bind(HelpFactory.NAME, new DefaultHelpFactory());
} catch (NamingException ex) {
throw new EmuException("Problem setting up InitialNaming bindings", ex);
}
}
/**
* Configure any command classes specified by a given plugin's descriptor
*
* @param pluginId the plugin to be processed
* @throws EmuException
*/
public void configurePlugin(String pluginId) throws EmuException {
if (!configuredPlugins.contains(pluginId)) {
XMLElement pluginDescriptor = loadPluginDescriptor(pluginId);
extractAliases(pluginDescriptor);
extractSyntaxBundles(pluginDescriptor);
configuredPlugins.add(pluginId);
}
}
/**
* Populate the supplied syntax manager with syntax entries from a plugin descriptor.
*
* @param pluginDescriptor the plugin descriptor's root XML element
* @throws EmuException
*/
private void extractSyntaxBundles(XMLElement pluginDescriptor)
throws EmuException {
XMLElement syntaxesDescriptor = findExtension(pluginDescriptor, SyntaxManager.SYNTAXES_EP_NAME);
if (syntaxesDescriptor == null) {
return;
}
SyntaxSpecLoader loader = new SyntaxSpecLoader();
for (XMLElement syntaxDescriptor : syntaxesDescriptor.getChildren()) {
if (!syntaxDescriptor.getName().equals("syntax")) {
continue;
}
SyntaxSpecAdapter adaptedElement = new XMLSyntaxSpecAdapter(syntaxDescriptor);
try {
SyntaxBundle bundle = loader.loadSyntax(adaptedElement);
if (bundle != null) {
syntaxMgr.add(bundle);
}
} catch (Exception ex) {
throw new EmuException("problem in syntax", ex);
}
}
}
/**
* Populate the supplied alias manager with aliases from a plugin descriptor.
*
* @param pluginDescriptor the plugin descriptor's root XML element
* @throws EmuException
*/
private void extractAliases(XMLElement pluginDescriptor) {
XMLElement aliasesDescriptor = findExtension(pluginDescriptor, AliasManager.ALIASES_EP_NAME);
if (aliasesDescriptor == null) {
return;
}
for (XMLElement aliasDescriptor : aliasesDescriptor.getChildren()) {
if (aliasDescriptor.getName().equals("alias")) {
String alias = aliasDescriptor.getStringAttribute("name");
String className = aliasDescriptor.getStringAttribute("class");
aliasMgr.add(alias, className);
}
}
}
/**
* Locate the descriptor for a given extension point.
*
* @param pluginDescriptor the plugin descriptor
* @param epName the extension point's name
* @return
*/
private XMLElement findExtension(XMLElement pluginDescriptor, String epName) {
for (XMLElement child : pluginDescriptor.getChildren()) {
if (child.getName().equals("extension") &&
epName.equals(child.getStringAttribute("point"))) {
return child;
}
}
return null;
}
/**
* Locate and load a plugin descriptor. We search the "descriptors" directory of
* each of the projects listed in ALL_PROJECTS
*
* @param pluginName the name of the plugin we're trying to locate
* @return the loaded plugin descriptor or <code>null</code>
* @throws EmuException
*/
private XMLElement loadPluginDescriptor(String pluginName)
throws EmuException {
File file = null;
for (String projectName : ALL_PROJECTS) {
file = new File(new File(new File(root, projectName), "descriptors"), pluginName + ".xml");
if (file.exists()) {
break;
}
}
if (!file.exists()) {
throw new EmuException("Cannot find plugin descriptor file for '" +
pluginName + "': " + file.getAbsolutePath());
}
BufferedReader br = null;
try {
XMLElement elem = new XMLElement();
br = new BufferedReader(new FileReader(file));
elem.parseFromReader(br);
if (!elem.getName().equals("plugin")) {
throw new EmuException("File does not contain a 'plugin' descriptor: " + file);
}
return elem;
} catch (IOException ex) {
throw new EmuException("Problem reading / parsing plugin descriptor file " + file, ex);
} finally {
try {
br.close();
} catch (IOException ex) {
// ignore
}
}
}
}