blob: c087bd24506aa5a22e266241cf0a5bbd2a9aa94a [file] [log] [blame]
/*
* Copyright 2000-2009 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 com.intellij.lang;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* The base class for all programming language support implementations. Specific language implementations should inherit from this class
* and its register instance wrapped with {@link com.intellij.openapi.fileTypes.LanguageFileType} instance through
* <code>FileTypeManager.getInstance().registerFileType</code>
* There should be exactly one instance of each Language.
* It is usually created when creating {@link com.intellij.openapi.fileTypes.LanguageFileType} and can be retrieved later
* with {@link #findInstance(Class)}.
* For the list of standard languages, see {@link com.intellij.lang.StdLanguages}.
*/
public abstract class Language extends UserDataHolderBase {
private static final Logger LOG = Logger.getInstance("#com.intellij.lang.Language");
private static final Map<Class<? extends Language>, Language> ourRegisteredLanguages =
Collections.synchronizedMap(new THashMap<Class<? extends Language>, Language>());
private static final Map<String, List<Language>> ourRegisteredMimeTypes =
Collections.synchronizedMap(new THashMap<String, List<Language>>());
private static final Map<String, Language> ourRegisteredIDs = new THashMap<String, Language>();
private final Language myBaseLanguage;
private final String myID;
private final String[] myMimeTypes;
private final List<Language> myDialects = ContainerUtil.createLockFreeCopyOnWriteList();
public static final Language ANY = new Language("") {
@Override
public String toString() {
//noinspection HardCodedStringLiteral
return "Language: ANY";
}
};
protected Language(@NotNull @NonNls String id) {
this(id, ArrayUtil.EMPTY_STRING_ARRAY);
}
protected Language(@NotNull @NonNls final String ID, @NotNull @NonNls final String... mimeTypes) {
this(null, ID, mimeTypes);
}
protected Language(@Nullable Language baseLanguage, @NotNull @NonNls final String ID, @NotNull @NonNls final String... mimeTypes) {
myBaseLanguage = baseLanguage;
myID = ID;
myMimeTypes = mimeTypes;
Class<? extends Language> langClass = getClass();
Language prev = ourRegisteredLanguages.put(langClass, this);
if (prev != null) {
LOG.error("Language of '" + langClass + "' is already registered: " + prev);
return;
}
prev = ourRegisteredIDs.put(ID, this);
if (prev != null) {
LOG.error("Language with ID '" + ID + "' is already registered: " + prev.getClass());
}
for (String mimeType : mimeTypes) {
if (StringUtil.isEmpty(mimeType)) {
continue;
}
List<Language> languagesByMimeType = ourRegisteredMimeTypes.get(mimeType);
if (languagesByMimeType == null) {
synchronized (ourRegisteredMimeTypes) {
languagesByMimeType = ourRegisteredMimeTypes.get(mimeType);
if (languagesByMimeType == null) {
languagesByMimeType = Collections.synchronizedList(new ArrayList<Language>());
ourRegisteredMimeTypes.put(mimeType, languagesByMimeType);
}
}
}
languagesByMimeType.add(this);
}
if (baseLanguage != null) {
baseLanguage.myDialects.add(this);
}
}
/**
* @return collection of all languages registered so far.
*/
public static Collection<Language> getRegisteredLanguages() {
final Collection<Language> languages = ourRegisteredLanguages.values();
return Collections.unmodifiableCollection(new ArrayList<Language>(languages));
}
/**
* @param klass <code>java.lang.Class</code> of the particular language. Serves key purpose.
* @return instance of the <code>klass</code> language registered if any.
*/
public static <T extends Language> T findInstance(Class<T> klass) {
//noinspection unchecked
return (T)ourRegisteredLanguages.get(klass);
}
/**
* @param mimeType of the particular language.
* @return collection of all languages for the given <code>mimeType</code>.
*/
@NotNull
public static Collection<Language> findInstancesByMimeType(@Nullable String mimeType) {
List<Language> result = mimeType != null ? ourRegisteredMimeTypes.get(mimeType) : null;
return result != null ? Collections.unmodifiableCollection(result) : Collections.<Language>emptyList();
}
public String toString() {
//noinspection HardCodedStringLiteral
return "Language: " + myID;
}
/**
* Returns the list of MIME types corresponding to the language. The language MIME type is used for specifying the base language
* of a JSP page.
*
* @return The list of MIME types.
*/
@NotNull
public String[] getMimeTypes() {
return myMimeTypes;
}
/**
* Returns a user-readable name of the language.
*
* @return the name of the language.
*/
@NotNull
public String getID() {
return myID;
}
@Nullable
public LanguageFileType getAssociatedFileType() {
final FileType[] types = FileTypeRegistry.getInstance().getRegisteredFileTypes();
for (final FileType fileType : types) {
if (fileType instanceof LanguageFileType && ((LanguageFileType)fileType).getLanguage() == this) {
return (LanguageFileType)fileType;
}
}
for (final FileType fileType : types) {
if (fileType instanceof LanguageFileType && isKindOf(((LanguageFileType)fileType).getLanguage())) {
return (LanguageFileType)fileType;
}
}
return null;
}
@Nullable
public Language getBaseLanguage() {
return myBaseLanguage;
}
public String getDisplayName() {
return getID();
}
public final boolean is(Language another) {
return this == another;
}
public boolean isCaseSensitive() {
return myBaseLanguage != null && myBaseLanguage.isCaseSensitive();
}
public final boolean isKindOf(Language another) {
Language l = this;
while (l != null) {
if (l.is(another)) return true;
l = l.getBaseLanguage();
}
return false;
}
public final boolean isKindOf(String anotherLanguageId) {
Language l = this;
while (l != null) {
if (l.getID().equals(anotherLanguageId)) return true;
l = l.getBaseLanguage();
}
return false;
}
public List<Language> getDialects() {
return myDialects;
}
@Nullable
public static Language findLanguageByID(String id) {
final Collection<Language> languages = getRegisteredLanguages();
for (Language language : languages) {
if (language.getID().equals(id)) {
return language;
}
}
return null;
}
/** Fake language identifier without registering */
protected Language(String id, @SuppressWarnings("UnusedParameters") boolean register) {
myID = id;
myBaseLanguage = null;
myMimeTypes = null;
}
}