/* * 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; } }