/* * Copyright 2000-2012 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.psi.tree; import com.intellij.lang.Language; import com.intellij.openapi.diagnostic.Logger; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Interface for token types returned from lexical analysis and for types * of nodes in the AST tree. All used element types are added to a registry which * can be enumerated or accessed by index. * * @see com.intellij.lexer.Lexer#getTokenType() * @see com.intellij.lang.ASTNode#getElementType() */ public class IElementType { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.tree.IElementType"); public static final IElementType[] EMPTY_ARRAY = new IElementType[0]; /** * Default enumeration predicate which matches all token types. * * @see #enumerate(Predicate) */ public static final Predicate TRUE = new Predicate() { @Override public boolean matches(IElementType type) { return true; } }; public static final short FIRST_TOKEN_INDEX = 1; public static final short MAX_INDEXED_TYPES = 15000; private static short ourCounter = FIRST_TOKEN_INDEX; private static IElementType[] ourRegistry = new IElementType[700]; private static final ReentrantReadWriteLock.ReadLock ourRegistryReadLock; private static final ReentrantReadWriteLock.WriteLock ourRegistryWriteLock; static { ReentrantReadWriteLock ourLock = new ReentrantReadWriteLock(); ourRegistryReadLock = ourLock.readLock(); ourRegistryWriteLock = ourLock.writeLock(); } private final short myIndex; @NotNull private final String myDebugName; @NotNull private final Language myLanguage; /** * Creates and registers a new element type for the specified language. * * @param debugName the name of the element type, used for debugging purposes. * @param language the language with which the element type is associated. */ public IElementType(@NotNull @NonNls String debugName, @Nullable Language language) { this(debugName, language, true); } protected IElementType(@NotNull @NonNls String debugName, @Nullable Language language, boolean register) { myDebugName = debugName; myLanguage = language == null ? Language.ANY : language; if (register) { //noinspection AssignmentToStaticFieldFromInstanceMethod ourRegistryWriteLock.lock(); try { myIndex = ourCounter++; LOG.assertTrue(myIndex < MAX_INDEXED_TYPES, "Too many element types registered. Out of (short) range."); final int registryIndex = myIndex - FIRST_TOKEN_INDEX; if (ourRegistry.length == registryIndex) { IElementType[] newRegistry = new IElementType[ourRegistry.length << 1]; System.arraycopy(ourRegistry, 0, newRegistry, 0, ourRegistry.length); ourRegistry = newRegistry; } ourRegistry[registryIndex] = this; // overflow } finally { ourRegistryWriteLock.unlock(); } } else { myIndex = -1; } } /** * Returns the language associated with the element type. * * @return the associated language. */ @NotNull public Language getLanguage() { return myLanguage; } /** * Returns the index of the element type in the table of all registered element * types. * * @return the element type index. */ public final short getIndex() { return myIndex; } public String toString() { return myDebugName; } /** * Controls whitespace balancing behavior of PsiBuilder. * <p>By default, empty composite elements (containing no children) are bounded to the right (previous) neighbour, forming following tree: * <pre> * [previous_element] * [whitespace] * [empty_element] * <empty> * [next_element] * </pre> * <p>Left-bound elements are bounded to the left (next) neighbour instead: * <pre> * [previous_element] * [empty_element] * <empty> * [whitespace] * [next_element] * </pre> * <p>See com.intellij.lang.impl.PsiBuilderImpl.prepareLightTree() for details. * @return true if empty elements of this type should be bound to the left. */ public boolean isLeftBound() { return false; } /** * Returns the element type registered at the specified index. * * @param idx the index for which the element type should be returned. * @return the element type at the specified index. * @throws IndexOutOfBoundsException if the index is out of registered elements' range. */ public static IElementType find(short idx) { ourRegistryReadLock.lock(); try { if (idx == 0) return ourRegistry[0]; // We've changed FIRST_TOKEN_INDEX from 0 to 1. This is just for old plugins to avoid crashes. if (idx >= ourCounter) return null; return ourRegistry[idx - FIRST_TOKEN_INDEX]; } finally { ourRegistryReadLock.unlock(); } } /** * Predicate for matching element types. * * @see IElementType#enumerate(Predicate) */ public interface Predicate { boolean matches(IElementType type); } static short getAllocatedTypesCount() { return ourCounter; } /** * Enumerates all registered token types which match the specified predicate. * * @param p the predicate which should be matched by the element types. * @return the array of matching element types. */ @NotNull public static IElementType[] enumerate(@NotNull Predicate p) { IElementType[] copy; ourRegistryReadLock.lock(); try { copy = new IElementType[ourCounter - FIRST_TOKEN_INDEX]; System.arraycopy(ourRegistry, 0, copy, 0, ourCounter - FIRST_TOKEN_INDEX); } finally { ourRegistryReadLock.unlock(); } List<IElementType> matches = new ArrayList<IElementType>(); for (IElementType value : copy) { if (p.matches(value)) { matches.add(value); } } return matches.toArray(new IElementType[matches.size()]); } }