/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.ide.script;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.ClassLoaderUtil;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringHash;
import com.intellij.util.Function;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.PooledThreadExecutor;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
class Jsr223IdeScriptEngineManagerImpl extends IdeScriptEngineManager {
private static final Logger LOG = Logger.getInstance(IdeScriptEngineManager.class);
private final Future<ScriptEngineManager> myManagerFuture = PooledThreadExecutor.INSTANCE.submit(new Callable<ScriptEngineManager>() {
@Override
public ScriptEngineManager call() {
long start = System.currentTimeMillis();
try {
return new ScriptEngineManager();
}
finally {
long end = System.currentTimeMillis();
LOG.info(ScriptEngineManager.class.getName() + " initialized in " + (end - start) + " ms");
}
}
});
@NotNull
@Override
public List<String> getLanguages() {
return ContainerUtil.map(getScriptEngineManager().getEngineFactories(), new Function<ScriptEngineFactory, String>() {
@Override
public String fun(ScriptEngineFactory factory) {
return factory.getLanguageName();
}
});
}
@NotNull
@Override
public List<String> getFileExtensions(@Nullable String language) {
List<String> extensions = ContainerUtil.newArrayList();
List<ScriptEngineFactory> factories = getScriptEngineManager().getEngineFactories();
for (ScriptEngineFactory factory : factories) {
if (language == null || factory.getLanguageName().equals(language)) {
extensions.addAll(factory.getExtensions());
}
}
return extensions;
}
@Nullable
@Override
public IdeScriptEngine getEngineForLanguage(@NotNull final String language, @Nullable ClassLoader loader) {
ClassLoader l = ObjectUtils.notNull(loader, AllPluginsLoader.INSTANCE);
return ClassLoaderUtil.runWithClassLoader(l, new Computable<IdeScriptEngine>() {
@Override
public IdeScriptEngine compute() {
return createIdeScriptEngine(getScriptEngineManager().getEngineByName(language));
}
});
}
@Nullable
@Override
public IdeScriptEngine getEngineForFileExtension(@NotNull final String extension, @Nullable ClassLoader loader) {
ClassLoader l = ObjectUtils.notNull(loader, AllPluginsLoader.INSTANCE);
return ClassLoaderUtil.runWithClassLoader(l, new Computable<IdeScriptEngine>() {
@Override
public IdeScriptEngine compute() {
return createIdeScriptEngine(getScriptEngineManager().getEngineByExtension(extension));
}
});
}
@Override
public boolean isInitialized() {
return myManagerFuture.isDone();
}
@NotNull
private ScriptEngineManager getScriptEngineManager() {
ScriptEngineManager manager = null;
try {
manager = myManagerFuture.get();
}
catch (Exception e) {
LOG.error(e);
}
return ObjectUtils.assertNotNull(manager);
}
@Nullable
private static IdeScriptEngine createIdeScriptEngine(@Nullable ScriptEngine engine) {
return engine == null ? null : redirectOutputToLog(new Jsr223IdeScriptEngine(engine));
}
private static IdeScriptEngine redirectOutputToLog(IdeScriptEngine engine) {
engine.setStdOut(new MyAbstractWriter() {
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
LOG.info(new String(cbuf, off, len));
}
});
engine.setStdErr(new MyAbstractWriter() {
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
LOG.warn(new String(cbuf, off, len));
}
});
return engine;
}
static class Jsr223IdeScriptEngine implements IdeScriptEngine {
private final ScriptEngine myEngine;
private final ClassLoader myLoader;
Jsr223IdeScriptEngine(ScriptEngine engine) {
myEngine = engine;
myLoader = Thread.currentThread().getContextClassLoader();
}
@Override
public Object getBinding(@NotNull String name) {
return myEngine.get(name);
}
@Override
public void setBinding(@NotNull String name, Object value) {
myEngine.put(name, value);
}
@NotNull
@Override
public Writer getStdOut() {
return myEngine.getContext().getWriter();
}
@Override
public void setStdOut(@NotNull Writer writer) {
myEngine.getContext().setWriter(writer);
}
@NotNull
@Override
public Writer getStdErr() {
return myEngine.getContext().getErrorWriter();
}
@Override
public void setStdErr(@NotNull Writer writer) {
myEngine.getContext().setErrorWriter(writer);
}
@NotNull
@Override
public Reader getStdIn() {
return myEngine.getContext().getReader();
}
@Override
public void setStdIn(@NotNull Reader reader) {
myEngine.getContext().setReader(reader);
}
@NotNull
@Override
public String getLanguage() {
return myEngine.getFactory().getLanguageName();
}
@NotNull
@Override
public List<String> getFileExtensions() {
return myEngine.getFactory().getExtensions();
}
@Override
public Object eval(@NotNull final String script) throws IdeScriptException {
return ClassLoaderUtil.runWithClassLoader(myLoader, new ThrowableComputable<Object, IdeScriptException>() {
@Override
public Object compute() throws IdeScriptException {
try {
return myEngine.eval(script);
}
catch (Throwable e) {
throw new IdeScriptException(e);
}
}
});
}
}
private static abstract class MyAbstractWriter extends Writer {
@Override
public void flush() throws IOException {
}
@Override
public void close() throws IOException {
}
}
static class AllPluginsLoader extends ClassLoader {
static final AllPluginsLoader INSTANCE = new AllPluginsLoader();
final Map<Long, ClassLoader> myLuckyGuess = ContainerUtil.newConcurrentMap();
public AllPluginsLoader() {
// Groovy performance: do not specify parent loader to enable our luckyGuesser
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//long ts = System.currentTimeMillis();
int p0 = name.indexOf("$");
int p1 = p0 > 0 ? name.indexOf("$", p0 + 1) : -1;
String base = p0 > 0 ? name.substring(0, Math.max(p0, p1)) : name;
long hash = StringHash.calc(base);
ClassLoader loader = myLuckyGuess.get(hash);
if (loader == this) throw new ClassNotFoundException(name);
Class<?> c = null;
if (loader != null) {
try {
c = loader.loadClass(name);
}
catch (ClassNotFoundException ignored) {
}
}
if (c == null) {
boolean first = true;
for (IdeaPluginDescriptor descriptor : PluginManagerCore.getPlugins()) {
ClassLoader l = descriptor.getPluginClassLoader();
if (l == null || l == loader) continue;
try {
l.loadClass(base);
if (first) {
myLuckyGuess.put(hash, l);
}
first = false;
try {
c = l.loadClass(name);
break;
}
catch (ClassNotFoundException e) {
if (p0 > 0) break;
if (name.startsWith("java.") || name.startsWith("groovy.")) break;
}
}
catch (ClassNotFoundException ignored) {
}
}
if (first && loader == null) {
myLuckyGuess.put(hash, this);
}
}
//LOG.info("AllPluginsLoader [" + StringUtil.formatDuration(System.currentTimeMillis() - ts) + "]: " + (c != null ? "+" : "-") + name);
if (c != null) return c;
myLuckyGuess.put(StringHash.calc(name), this);
throw new ClassNotFoundException(name);
}
@Override
protected URL findResource(String name) {
return getClass().getClassLoader().getResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) throws IOException {
return getClass().getClassLoader().getResources(name);
}
}
}