/*
* Copyright 2003-2017 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 jetbrains.mps.smodel.language;
import jetbrains.mps.smodel.BootstrapLanguages;
import jetbrains.mps.smodel.adapter.ids.SLanguageId;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import jetbrains.mps.smodel.runtime.ILanguageAspect;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.module.SModuleReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Runtime representation of a language, extension point for various language aspects.
* Dependency from typesystem, find usages and other language aspects is transitional, eventually (after 3.2)
* this class shall be generic and aware of {@link jetbrains.mps.smodel.runtime.ILanguageAspect} only.
* It shall not load any classes through reflection (any class-loading of generated code/aspects is responsibility of
* generated language runtime class).
* <p/>
* Language runtime keeps track of aspects queried (instantiates them lazily).
*/
public abstract class LanguageRuntime {
private final ConcurrentMap<Class<? extends ILanguageAspect>, ILanguageAspect> myAspectDescriptors = new ConcurrentHashMap<>();
// FIXME AP: is there a contract on duplication????
private final List<LanguageRuntime> myExtendingLanguages = new ArrayList<>();
private final List<LanguageRuntime> myExtendedLanguages = new ArrayList<>();
/**
* @return full name of the language, never {@code null}.
*/
public abstract String getNamespace();
/**
* @return identity of the language, never {@code null}. Generated LanguageRuntime classes shall override return value
*/
public abstract SLanguageId getId(); // FIXME supply a cons that takes mandatory values (id, name), rather than overriding methods in generated classes
/**
* Generated LanguageRuntime classes shall override this method
* Denoted with @ToRemove just to ease later discovery, it's method implementation to be removed, not the method itself
*
* @return 0 now
* @since 3.2
*/
public abstract int getVersion();
public Collection<? extends GeneratorRuntime> getGenerators() {
ArrayList<GeneratorRuntime> rv = new ArrayList<>(4);
populateRegisteredGenerators(rv);
return rv;
}
/**
* Provide aspect instance associated with the language. Aspect is instantiated only once, lazily (the first time asked)
* and the same instance is returned for each subsequent calls.
* <p>
* At the moment, sole mechanism to supply new aspect is code in generated language runtime subclass (i.e. there's no mechanism yet to
* add aspects dynamically).
* <p>
* Calls {@link LanguageRuntimeAware#setLanguageRuntime(LanguageRuntime)} on aspects implementing {@link LanguageRuntimeAware} after creation, passing itself
* as the parameter.
*
* @param aspectClass identifies aspect to retrieve
* @param <T> subtype of {@link jetbrains.mps.smodel.runtime.ILanguageAspect}
* @return instance of aspect implementation if there's one for the language
* @see #createAspect(Class)
* @see jetbrains.mps.smodel.runtime.ILanguageAspect
*/
public final <T extends ILanguageAspect> T getAspect(@NotNull Class<T> aspectClass) {
try {
T aspectDescriptor = aspectClass.cast(myAspectDescriptors.get(aspectClass));
if (aspectDescriptor == null) {
aspectDescriptor = createAspect(aspectClass);
if (aspectDescriptor == null) {
return null;
}
if (aspectDescriptor instanceof LanguageRuntimeAware) {
((LanguageRuntimeAware) aspectDescriptor).setLanguageRuntime(this);
}
T alreadyThere = aspectClass.cast(myAspectDescriptors.putIfAbsent(aspectClass, aspectDescriptor));
if (alreadyThere != null) {
return alreadyThere;
}
}
return aspectDescriptor;
} catch (Throwable th) {
String msg = String.format("Failed to instantiate aspect %s in language %s", aspectClass, getNamespace());
Logger.getLogger(LanguageRuntime.class).error(msg, th);
return null;
}
}
/**
* Method every language shall implement to tell its capabilities.
* Implementation doesn't need to keep state, {@link #getAspect(Class)} does that.
* @param aspectClass never null identifying interface of the aspect
* @param <T> aspect class
* @return may return {@code null} indicating language has no such aspect
*/
protected abstract <T extends ILanguageAspect> T createAspect(Class<T> aspectClass);
/*
* perhaps, could use WeakHashMap, although proper registration/un-registration sequence shall enforce no stale entries
*/
private final Map<SModuleReference, GeneratorRuntime> myRegisteredGenerators = new HashMap<>();
protected final void populateRegisteredGenerators(List<? super GeneratorRuntime> consumer) {
consumer.addAll(myRegisteredGenerators.values());
}
/*package*/ void register(GeneratorRuntime runtime) {
myRegisteredGenerators.put(runtime.getModuleReference(), runtime);
}
/*package*/ void unregister(GeneratorRuntime runtime) {
myRegisteredGenerators.remove(runtime.getModuleReference());
}
/**
* Closure of all languages that extend this one, exclusive.
*
* @return unmodifiable collection of languages
*/
@NotNull
public Iterable<LanguageRuntime> getExtendingLanguages() {
return myExtendingLanguages;
}
/**
* Closure of all languages this language extends, exclusive.
* Referenced languages are from the same LanguageRegistry as this one.
* (Although there's only one LanguageRegistry at the moment, it's likely to change in the future)
* <p/>
* Collection captures only languages actually available, and might not reflect all dependencies of the language, i.e.
* presents state of language relationship through a LanguageRegistry perspective. E.g. if language descriptor states 'extends' dependency
* from a language missing in the LanguageRegistry instance, that extended language will be ignored and collection returned won't mention it.
*
* @return unmodifiable collection of languages
*/
@NotNull
public Collection<LanguageRuntime> getExtendedLanguages() {
return Collections.unmodifiableCollection(myExtendedLanguages);
}
/**
* @deprecated override {@link #fillExtendedLanguages(Collection)} instead.
*/
@Deprecated
@ToRemove(version = 3.5)
protected String[] getExtendedLanguageIDs() {
// non-abstract to facilitate transition
return new String[0];
}
/**
* Subclasses shall override to report languages they extend (fill in supplied collection).
* It's not necessary to override the method if language doesn't extend any other, not to invoke super, this method is no-op.
* <p/>
* DESIGN NOTE: while it's sufficient to know SLanguageId only, I stick to SLanguage to keep namespace as debug information.
* Perhaps, shall pass an object that could take different alternatives (e.g. SLanguageId, (long,long), module reference)?
*/
protected void fillExtendedLanguages(Collection<SLanguage> extendedLanguages) {
// intentionally non-abstract and no-op
}
private void registerExtendingLanguage(LanguageRuntime extendingLanguage) {
myExtendingLanguages.add(extendingLanguage);
extendingLanguage.myExtendedLanguages.add(this);
}
@ToRemove(version = 3.5)
private void fillLegacyExtendedLanguages(Collection<SLanguage> extendedLanguages, LanguageRegistry registry) {
for (String namespace : getExtendedLanguageIDs()) {
LanguageRuntime l = registry.getLanguage(namespace);
if (l != null) {
extendedLanguages.add(MetaAdapterFactory.getLanguage(l.getId(), l.getNamespace()));
}
}
}
void initialize(LanguageRegistry registry) {
Queue<SLanguage> extendedLanguageIDs = new ArrayDeque<>();
fillExtendedLanguages(extendedLanguageIDs);
fillLegacyExtendedLanguages(extendedLanguageIDs, registry);
Set<SLanguageId> visitedLanguages = new HashSet<>();
visitedLanguages.add(getId());
while (!extendedLanguageIDs.isEmpty()) {
SLanguage nextLanguageID = extendedLanguageIDs.remove();
LanguageRuntime extendedLanguage = registry.getLanguage(nextLanguageID);
if (extendedLanguage != null && visitedLanguages.add(extendedLanguage.getId())) {
extendedLanguage.registerExtendingLanguage(this);
extendedLanguage.fillExtendedLanguages(extendedLanguageIDs);
extendedLanguage.fillLegacyExtendedLanguages(extendedLanguageIDs, registry);
}
}
// generally, should never happen, but doesn't hurt to ensure exclusive contract of getExtended/getExtendingLanguages()
myExtendingLanguages.remove(this);
myExtendedLanguages.remove(this);
// Here's a copy of the hack from smodel.Language#getExtendedLanguageRefs(). We used to manifest lang.core
// as extended language for any other language module, and once we switched to SLanguage, shall do the same at least for compatibility reasons.
// Once we generate this extends inside #getExtendedLanguageIDs() (better the new one that yield SLanguage instead of String),
// AND there are no old LanguageRuntime classes (i.e. past MPS 3.3), the hack shall cease to exist.
//
// XXX OTOH, does it make sense to force generation of explicit extends lang.core in each language?
LanguageRuntime langCore = registry.getLanguage(BootstrapLanguages.getLangCore());
assert langCore != null;
if (this != langCore && !visitedLanguages.contains(langCore.getId())) {
langCore.registerExtendingLanguage(this);
}
}
void deinitialize() {
myExtendingLanguages.clear();
myExtendedLanguages.clear();
}
@Override
public String toString() {
return getNamespace() + " runtime";
}
}