/* * Copyright 2010-2016 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 org.jetbrains.kotlin.asJava.builder; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.compiled.InnerClassSourceStrategy; import com.intellij.psi.impl.compiled.StubBuildingVisitor; import com.intellij.psi.impl.java.stubs.PsiClassStub; import com.intellij.psi.impl.java.stubs.PsiJavaFileStub; import com.intellij.psi.stubs.StubBase; import com.intellij.psi.stubs.StubElement; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.codegen.AbstractClassBuilder; import org.jetbrains.kotlin.fileClasses.OldPackageFacadeClassUtils; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.org.objectweb.asm.ClassVisitor; import org.jetbrains.org.objectweb.asm.FieldVisitor; import org.jetbrains.org.objectweb.asm.MethodVisitor; import java.util.List; public class StubClassBuilder extends AbstractClassBuilder { private static final InnerClassSourceStrategy<Object> EMPTY_STRATEGY = new InnerClassSourceStrategy<Object>() { @Override public Object findInnerClass(String s, Object o) { return null; } @Override public void accept(Object innerClass, StubBuildingVisitor<Object> visitor) { throw new UnsupportedOperationException("Shall not be called!"); } }; private final StubElement parent; private final PsiJavaFileStub fileStub; private StubBuildingVisitor v; private final Stack<StubElement> parentStack; private boolean isPackageClass = false; private int memberIndex = 0; public StubClassBuilder(@NotNull Stack<StubElement> parentStack, @NotNull PsiJavaFileStub fileStub) { this.parentStack = parentStack; this.parent = parentStack.peek(); this.fileStub = fileStub; } @NotNull @Override public ClassVisitor getVisitor() { assert v != null : "Called before class is defined"; return v; } @Override public void defineClass( PsiElement origin, int version, int access, @NotNull String name, @Nullable String signature, @NotNull String superName, @NotNull String[] interfaces ) { assert v == null : "defineClass() called twice?"; v = new StubBuildingVisitor<>(null, EMPTY_STRATEGY, parent, access, calculateShortName(name)); super.defineClass(origin, version, access, name, signature, superName, interfaces); if (origin instanceof KtFile) { FqName packageName = ((KtFile) origin).getPackageFqName(); String packageClassName = OldPackageFacadeClassUtils.getPackageClassName(packageName); if (name.equals(packageClassName) || name.endsWith("/" + packageClassName)) { isPackageClass = true; } } if (!isPackageClass) { parentStack.push(v.getResult()); } ((StubBase) v.getResult()).putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightClassOrigin(origin)); } @Nullable private String calculateShortName(@NotNull String internalName) { if (parent instanceof PsiJavaFileStub) { assert parent == fileStub; String packagePrefix = getPackageInternalNamePrefix(); assert internalName.startsWith(packagePrefix) : internalName + " : " + packagePrefix; return internalName.substring(packagePrefix.length()); } if (parent instanceof PsiClassStub<?>) { String parentPrefix = getClassInternalNamePrefix((PsiClassStub) parent); if (parentPrefix == null) return null; assert internalName.startsWith(parentPrefix) : internalName + " : " + parentPrefix; return internalName.substring(parentPrefix.length()); } return null; } @Nullable private String getClassInternalNamePrefix(@NotNull PsiClassStub classStub) { String packageName = fileStub.getPackageName(); String classStubQualifiedName = classStub.getQualifiedName(); if (classStubQualifiedName == null) return null; if (packageName.isEmpty()) { return classStubQualifiedName.replace('.', '$') + "$"; } else { return packageName.replace('.', '/') + "/" + classStubQualifiedName.substring(packageName.length() + 1).replace('.', '$') + "$"; } } @NotNull private String getPackageInternalNamePrefix() { String packageName = fileStub.getPackageName(); if (packageName.isEmpty()) { return ""; } else { return packageName.replace('.', '/') + "/"; } } @NotNull @Override public MethodVisitor newMethod( @NotNull JvmDeclarationOrigin origin, int access, @NotNull String name, @NotNull String desc, @Nullable String signature, @Nullable String[] exceptions ) { MethodVisitor internalVisitor = super.newMethod(origin, access, name, desc, signature, exceptions); if (internalVisitor != EMPTY_METHOD_VISITOR) { // If stub for method generated markLastChild(origin); } return internalVisitor; } @NotNull @Override public FieldVisitor newField( @NotNull JvmDeclarationOrigin origin, int access, @NotNull String name, @NotNull String desc, @Nullable String signature, @Nullable Object value ) { FieldVisitor internalVisitor = super.newField(origin, access, name, desc, signature, value); if (internalVisitor != EMPTY_FIELD_VISITOR) { // If stub for field generated markLastChild(origin); } return internalVisitor; } private void markLastChild(@NotNull JvmDeclarationOrigin origin) { List children = v.getResult().getChildrenStubs(); StubBase last = (StubBase) children.get(children.size() - 1); LightElementOrigin oldOrigin = last.getUserData(ClsWrapperStubPsiFactory.ORIGIN); if (oldOrigin != null) { PsiElement originalElement = oldOrigin.getOriginalElement(); throw new IllegalStateException("Rewriting origin element: " + (originalElement != null ? originalElement.getText() : null) + " for stub " + last.toString()); } last.putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightMemberOrigin(origin)); last.putUserData(MemberIndex.KEY, new MemberIndex(memberIndex++)); } @Override public void done() { if (!isPackageClass) { StubElement pop = parentStack.pop(); assert pop == v.getResult() : "parentStack: got " + pop + ", expected " + v.getResult(); } super.done(); } }