/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.api.vm;
import static com.oracle.truffle.api.vm.PolyglotEngine.LOG;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.vm.PolyglotEngine.Access;
/**
* Ahead-of-time initialization. If the JVM is started with {@link TruffleOptions#AOT}, it populates
* cache with languages found in application classloader.
*/
final class LanguageCache implements Comparable<LanguageCache> {
private static final boolean PRELOAD;
private static final Map<String, LanguageCache> CACHE;
private static volatile Map<String, LanguageCache> cache;
static final Collection<ClassLoader> AOT_LOADERS = TruffleOptions.AOT ? Access.loaders() : null;
private final String className;
private final Set<String> mimeTypes;
private final String name;
private final String version;
private final boolean interactive;
private final ClassLoader loader;
private final TruffleLanguage<?> singletonLanguage;
private volatile Class<? extends TruffleLanguage<?>> languageClass;
static {
CACHE = TruffleOptions.AOT ? initializeLanguages(loader()) : null;
PRELOAD = CACHE != null;
}
/**
* This method initializes all languages under the provided classloader.
*
* NOTE: Method's signature should not be changed as it is reflectively invoked from AOT
* compilation.
*
* @param loader The classloader to be used for finding languages.
* @return A map of initialized languages.
*/
private static Map<String, LanguageCache> initializeLanguages(final ClassLoader loader) {
return createLanguages(loader);
}
private LanguageCache(String prefix, Properties info, ClassLoader loader) {
this.loader = loader;
this.className = info.getProperty(prefix + "className");
this.name = info.getProperty(prefix + "name");
this.version = info.getProperty(prefix + "version");
TreeSet<String> ts = new TreeSet<>();
for (int i = 0;; i++) {
String mt = info.getProperty(prefix + "mimeType." + i);
if (mt == null) {
break;
}
ts.add(mt);
}
this.mimeTypes = Collections.unmodifiableSet(ts);
this.interactive = Boolean.valueOf(info.getProperty(prefix + "interactive"));
if (PRELOAD) {
this.languageClass = loadLanguageClass();
this.singletonLanguage = readSingleton(languageClass);
} else {
this.languageClass = null;
this.singletonLanguage = null;
}
}
private static ClassLoader loader() {
ClassLoader l;
if (PolyglotEngine.JDK8OrEarlier) {
l = PolyglotEngine.class.getClassLoader();
if (l == null) {
l = ClassLoader.getSystemClassLoader();
}
} else {
l = ModuleResourceLocator.createLoader();
}
return l;
}
static Map<String, LanguageCache> languages() {
if (PRELOAD) {
return CACHE;
}
if (cache == null) {
synchronized (LanguageCache.class) {
if (cache == null) {
cache = createLanguages(null);
}
}
}
return cache;
}
private static Map<String, LanguageCache> createLanguages(ClassLoader additionalLoader) {
List<LanguageCache> caches = new ArrayList<>();
for (ClassLoader loader : (AOT_LOADERS == null ? Access.loaders() : AOT_LOADERS)) {
collectLanguages(loader, caches);
}
if (additionalLoader != null) {
collectLanguages(additionalLoader, caches);
}
Map<String, LanguageCache> seenClasses = new HashMap<>();
for (LanguageCache languageCache : caches) {
seenClasses.put(languageCache.className, languageCache);
}
Map<String, LanguageCache> cacheToMimeType = new HashMap<>();
for (LanguageCache languageCache : caches) {
for (String mimeType : languageCache.getMimeTypes()) {
cacheToMimeType.put(mimeType, languageCache);
}
}
return cacheToMimeType;
}
private static void collectLanguages(ClassLoader loader, List<LanguageCache> list) {
if (loader == null) {
return;
}
Enumeration<URL> en;
try {
en = loader.getResources("META-INF/truffle/language");
} catch (IOException ex) {
throw new IllegalStateException("Cannot read list of Truffle languages", ex);
}
while (en.hasMoreElements()) {
URL u = en.nextElement();
Properties p;
try {
p = new Properties();
try (InputStream is = u.openStream()) {
p.load(is);
}
} catch (IOException ex) {
LOG.log(Level.CONFIG, "Cannot process " + u + " as language definition", ex);
continue;
}
for (int cnt = 1;; cnt++) {
String prefix = "language" + cnt + ".";
String name = p.getProperty(prefix + "name");
if (name == null) {
break;
}
list.add(new LanguageCache(prefix, p, loader));
}
}
}
public int compareTo(LanguageCache o) {
return className.compareTo(o.className);
}
Set<String> getMimeTypes() {
return mimeTypes;
}
String getName() {
return name;
}
String getVersion() {
return version;
}
String getClassName() {
return className;
}
boolean isInteractive() {
return interactive;
}
LoadedLanguage loadLanguage() {
TruffleLanguage<?> instance;
boolean singleton = true;
try {
if (PRELOAD) {
instance = singletonLanguage;
if (instance == null) {
instance = this.languageClass.newInstance();
singleton = false;
}
} else {
Class<? extends TruffleLanguage<?>> clazz = loadLanguageClass();
try {
instance = readSingleton(clazz);
if (instance == null) {
instance = clazz.newInstance();
singleton = false;
}
} catch (Exception e) {
throw e;
}
}
} catch (Exception e) {
throw new IllegalStateException("Cannot create instance of " + name + " language implementation. Public default constructor expected in " + className + ".", e);
}
return new LoadedLanguage(instance, singleton);
}
@SuppressWarnings("unchecked")
private Class<? extends TruffleLanguage<?>> loadLanguageClass() {
if (languageClass == null) {
synchronized (this) {
if (languageClass == null) {
try {
languageClass = (Class<? extends TruffleLanguage<?>>) Class.forName(className, true, loader);
} catch (Exception e) {
throw new IllegalStateException("Cannot load language " + name + ". Language implementation class " + className + " failed to load.", e);
}
}
}
}
return languageClass;
}
private static TruffleLanguage<?> readSingleton(Class<?> languageClass) {
try {
return (TruffleLanguage<?>) languageClass.getField("INSTANCE").get(null);
} catch (Exception ex) {
return null;
}
}
static final class LoadedLanguage {
private final TruffleLanguage<?> language;
private final boolean singleton;
LoadedLanguage(TruffleLanguage<?> language, boolean singleton) {
this.singleton = singleton;
this.language = language;
}
TruffleLanguage<?> getLanguage() {
return language;
}
boolean isSingleton() {
return singleton;
}
}
}