/* * Copyright 2000-2013 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.impl.compiled; import com.intellij.core.JavaCoreBundle; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleSettingsFacade; import com.intellij.psi.impl.PsiElementBase; import com.intellij.psi.impl.source.SourceTreeToPsiMap; import com.intellij.psi.impl.source.tree.TreeElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.List; public abstract class ClsElementImpl extends PsiElementBase implements PsiCompiledElement { public static final Key<PsiCompiledElement> COMPILED_ELEMENT = Key.create("COMPILED_ELEMENT"); protected static final String CAN_NOT_MODIFY_MESSAGE = JavaCoreBundle.message("psi.error.attempt.to.edit.class.file"); private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.compiled.ClsElementImpl"); private volatile TreeElement myMirror = null; @Override @NotNull public Language getLanguage() { return JavaLanguage.INSTANCE; } @Override public PsiManager getManager() { return getParent().getManager(); } @Override public PsiFile getContainingFile() { PsiElement parent = getParent(); if (parent == null) { throw new PsiInvalidElementAccessException(this); } return parent.getContainingFile(); } @Override public final boolean isWritable() { return false; } @Override public boolean isPhysical() { return true; } @Override public boolean isValid() { PsiElement parent = getParent(); return parent != null && parent.isValid(); } @Override public PsiElement copy() { return this; } @NotNull protected PsiElement[] getChildren(@Nullable PsiElement... children) { if (children == null) { return PsiElement.EMPTY_ARRAY; } List<PsiElement> list = ContainerUtil.newArrayListWithExpectedSize(children.length); for (PsiElement child : children) { if (child != null) { list.add(child); } } return PsiUtilCore.toPsiElementArray(list); } @Override public void checkAdd(@NotNull PsiElement element) throws IncorrectOperationException { throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE); } @Override public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException { throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE); } @Override public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE); } @Override public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException { throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE); } @Override public void delete() throws IncorrectOperationException { throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE); } @Override public void checkDelete() throws IncorrectOperationException { throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE); } @Override public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException { throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE); } public abstract void appendMirrorText(int indentLevel, @NotNull StringBuilder buffer); protected int getIndentSize() { return JavaCodeStyleSettingsFacade.getInstance(getProject()).getIndentSize(); } public abstract void setMirror(@NotNull TreeElement element) throws InvalidMirrorException; @Override public PsiElement getMirror() { TreeElement mirror = myMirror; if (mirror == null) { ((ClsFileImpl)getContainingFile()).getMirror(); mirror = myMirror; } return SourceTreeToPsiMap.treeElementToPsi(mirror); } @Override public final TextRange getTextRange() { PsiElement mirror = getMirror(); return mirror != null ? mirror.getTextRange() : TextRange.EMPTY_RANGE; } @Override public final int getStartOffsetInParent() { PsiElement mirror = getMirror(); return mirror != null ? mirror.getStartOffsetInParent() : -1; } @Override public int getTextLength() { String text = getText(); return text == null ? 0 : text.length(); } @Override public PsiElement findElementAt(int offset) { PsiElement mirrorAt = getMirror().findElementAt(offset); while (true) { if (mirrorAt == null) return null; PsiElement elementAt = mirrorToElement(mirrorAt); if (elementAt != null) return elementAt; mirrorAt = mirrorAt.getParent(); } } @Override public PsiReference findReferenceAt(int offset) { PsiReference mirrorRef = getMirror().findReferenceAt(offset); if (mirrorRef == null) return null; PsiElement mirrorElement = mirrorRef.getElement(); PsiElement element = mirrorToElement(mirrorElement); if (element == null) return null; return element.getReference(); } @Nullable private PsiElement mirrorToElement(PsiElement mirror) { final PsiElement m = getMirror(); if (m == mirror) return this; PsiElement[] children = getChildren(); if (children.length == 0) return null; for (PsiElement child : children) { ClsElementImpl clsChild = (ClsElementImpl)child; if (PsiTreeUtil.isAncestor(clsChild.getMirror(), mirror, false)) { PsiElement element = clsChild.mirrorToElement(mirror); if (element != null) return element; } } return null; } @Override public final int getTextOffset() { PsiElement mirror = getMirror(); return mirror != null ? mirror.getTextOffset() : -1; } @Override public String getText() { PsiElement mirror = getMirror(); if (mirror != null) return mirror.getText(); StringBuilder buffer = new StringBuilder(); appendMirrorText(0, buffer); LOG.error("Mirror wasn't set for " + this + ", expected text:\n" + buffer); return buffer.toString(); } @Override @NotNull public char[] textToCharArray() { return getMirror().textToCharArray(); } @Override public boolean textMatches(@NotNull CharSequence text) { return getText().equals(text.toString()); } @Override public boolean textMatches(@NotNull PsiElement element) { return getText().equals(element.getText()); } @Override public ASTNode getNode() { return null; } protected static void goNextLine(int indentLevel, @NotNull StringBuilder buffer) { buffer.append('\n'); for (int i = 0; i < indentLevel; i++) buffer.append(' '); } protected static void appendText(@NotNull PsiElement stub, int indentLevel, @NotNull StringBuilder buffer) { ((ClsElementImpl)stub).appendMirrorText(indentLevel, buffer); } protected static final String NEXT_LINE = "go_to_next_line_and_indent"; protected static void appendText(@Nullable PsiElement stub, int indentLevel, @NotNull StringBuilder buffer, @NotNull String separator) { if (stub == null) return; int pos = buffer.length(); ((ClsElementImpl)stub).appendMirrorText(indentLevel, buffer); if (buffer.length() != pos) { if (separator == NEXT_LINE) { goNextLine(indentLevel, buffer); } else { buffer.append(separator); } } } protected void setMirrorCheckingType(@NotNull TreeElement element, @Nullable IElementType type) throws InvalidMirrorException { // uncomment for extended consistency check //if (myMirror != null) { // throw new InvalidMirrorException("Mirror should be null: " + myMirror); //} if (type != null && element.getElementType() != type) { throw new InvalidMirrorException(element.getElementType() + " != " + type); } element.getPsi().putUserData(COMPILED_ELEMENT, this); myMirror = element; } protected static <T extends PsiElement> void setMirror(@Nullable T stub, @Nullable T mirror) throws InvalidMirrorException { if (stub == null || mirror == null) { throw new InvalidMirrorException(stub, mirror); } ((ClsElementImpl)stub).setMirror(SourceTreeToPsiMap.psiToTreeNotNull(mirror)); } protected static <T extends PsiElement> void setMirrorIfPresent(@Nullable T stub, @Nullable T mirror) throws InvalidMirrorException { if ((stub == null) != (mirror == null)) { throw new InvalidMirrorException(stub, mirror); } else if (stub != null) { ((ClsElementImpl)stub).setMirror(SourceTreeToPsiMap.psiToTreeNotNull(mirror)); } } protected static <T extends PsiElement> void setMirrors(@NotNull T[] stubs, @NotNull T[] mirrors) throws InvalidMirrorException { setMirrors(Arrays.asList(stubs), Arrays.asList(mirrors)); } protected static <T extends PsiElement> void setMirrors(@NotNull List<T> stubs, @NotNull T[] mirrors) throws InvalidMirrorException { setMirrors(stubs, Arrays.asList(mirrors)); } protected static <T extends PsiElement> void setMirrors(@NotNull List<T> stubs, @NotNull List<T> mirrors) throws InvalidMirrorException { if (stubs.size() != mirrors.size()) { throw new InvalidMirrorException(stubs, mirrors); } for (int i = 0; i < stubs.size(); i++) { setMirror(stubs.get(i), mirrors.get(i)); } } protected static class InvalidMirrorException extends RuntimeException { public InvalidMirrorException(@NotNull @NonNls String message) { super(message); } public InvalidMirrorException(@Nullable PsiElement stubElement, @Nullable PsiElement mirrorElement) { this("stub:" + stubElement + "; mirror:" + mirrorElement); } public InvalidMirrorException(@NotNull PsiElement[] stubElements, @NotNull PsiElement[] mirrorElements) { this("stub:" + Arrays.toString(stubElements) + "; mirror:" + Arrays.toString(mirrorElements)); } public InvalidMirrorException(@NotNull List<? extends PsiElement> stubElements, @NotNull List<? extends PsiElement> mirrorElements) { this("stub:" + stubElements + "; mirror:" + mirrorElements); } } }