001    /*
002     * Copyright 2010-2016 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.asJava.builder;
018    
019    import com.intellij.psi.PsiElement;
020    import com.intellij.psi.impl.compiled.InnerClassSourceStrategy;
021    import com.intellij.psi.impl.compiled.StubBuildingVisitor;
022    import com.intellij.psi.impl.java.stubs.PsiClassStub;
023    import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
024    import com.intellij.psi.stubs.StubBase;
025    import com.intellij.psi.stubs.StubElement;
026    import com.intellij.util.containers.Stack;
027    import org.jetbrains.annotations.NotNull;
028    import org.jetbrains.annotations.Nullable;
029    import org.jetbrains.kotlin.fileClasses.OldPackageFacadeClassUtils;
030    import org.jetbrains.kotlin.codegen.AbstractClassBuilder;
031    import org.jetbrains.kotlin.name.FqName;
032    import org.jetbrains.kotlin.psi.KtFile;
033    import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
034    import org.jetbrains.org.objectweb.asm.ClassVisitor;
035    import org.jetbrains.org.objectweb.asm.FieldVisitor;
036    import org.jetbrains.org.objectweb.asm.MethodVisitor;
037    
038    import java.util.List;
039    
040    public class StubClassBuilder extends AbstractClassBuilder {
041        private static final InnerClassSourceStrategy<Object> EMPTY_STRATEGY = new InnerClassSourceStrategy<Object>() {
042            @Override
043            public Object findInnerClass(String s, Object o) {
044                return null;
045            }
046    
047            @Override
048            public void accept(Object innerClass, StubBuildingVisitor<Object> visitor) {
049                throw new UnsupportedOperationException("Shall not be called!");
050            }
051        };
052        private final StubElement parent;
053        private StubBuildingVisitor v;
054        private final Stack<StubElement> parentStack;
055        private boolean isPackageClass = false;
056    
057        public StubClassBuilder(@NotNull Stack<StubElement> parentStack) {
058            this.parentStack = parentStack;
059            this.parent = parentStack.peek();
060        }
061    
062        @NotNull
063        @Override
064        public ClassVisitor getVisitor() {
065            assert v != null : "Called before class is defined";
066            return v;
067        }
068    
069        @Override
070        public void defineClass(
071                PsiElement origin,
072                int version,
073                int access,
074                @NotNull String name,
075                @Nullable String signature,
076                @NotNull String superName,
077                @NotNull String[] interfaces
078        ) {
079            assert v == null : "defineClass() called twice?";
080    
081            v = new StubBuildingVisitor<Object>(null, EMPTY_STRATEGY, parent, access, calculateShortName(name));
082    
083            super.defineClass(origin, version, access, name, signature, superName, interfaces);
084    
085            if (origin instanceof KtFile) {
086                FqName packageName = ((KtFile) origin).getPackageFqName();
087                String packageClassName = OldPackageFacadeClassUtils.getPackageClassName(packageName);
088    
089                if (name.equals(packageClassName) || name.endsWith("/" + packageClassName)) {
090                    isPackageClass = true;
091                }
092            }
093    
094            if (!isPackageClass) {
095                parentStack.push(v.getResult());
096            }
097    
098            ((StubBase) v.getResult()).putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightClassOrigin(origin));
099        }
100    
101        @Nullable
102        private String calculateShortName(@NotNull String internalName) {
103            if (parent instanceof PsiJavaFileStub) {
104                String packagePrefix = getPackageInternalNamePrefix((PsiJavaFileStub) parent);
105                assert internalName.startsWith(packagePrefix) : internalName + " : " + packagePrefix;
106                return internalName.substring(packagePrefix.length());
107            }
108            if (parent instanceof PsiClassStub<?>) {
109                String parentPrefix = getClassInternalNamePrefix((PsiClassStub) parent);
110                if (parentPrefix == null) return null;
111    
112                assert internalName.startsWith(parentPrefix) : internalName + " : " + parentPrefix;
113                return internalName.substring(parentPrefix.length());
114            }
115            return null;
116        }
117    
118        @Nullable
119        private String getClassInternalNamePrefix(@NotNull PsiClassStub classStub) {
120            PsiJavaFileStub fileStub = (PsiJavaFileStub) parentStack.get(0);
121    
122            String packageName = fileStub.getPackageName();
123    
124            String classStubQualifiedName = classStub.getQualifiedName();
125            if (classStubQualifiedName == null) return null;
126    
127            if (packageName.isEmpty()) {
128                return classStubQualifiedName.replace('.', '$') + "$";
129            }
130            else {
131                return packageName.replace('.', '/') + "/" + classStubQualifiedName.substring(packageName.length() + 1).replace('.', '$') + "$";
132            }
133        }
134    
135    
136        @NotNull
137        private static String getPackageInternalNamePrefix(@NotNull PsiJavaFileStub fileStub) {
138            String packageName = fileStub.getPackageName();
139            if (packageName.isEmpty()) {
140                return "";
141            }
142            else {
143                return packageName.replace('.', '/') + "/";
144            }
145        }
146    
147        @NotNull
148        @Override
149        public MethodVisitor newMethod(
150                @NotNull JvmDeclarationOrigin origin,
151                int access,
152                @NotNull String name,
153                @NotNull String desc,
154                @Nullable String signature,
155                @Nullable String[] exceptions
156        ) {
157            MethodVisitor internalVisitor = super.newMethod(origin, access, name, desc, signature, exceptions);
158    
159            if (internalVisitor != EMPTY_METHOD_VISITOR) {
160                // If stub for method generated
161                markLastChild(origin);
162            }
163    
164            return internalVisitor;
165        }
166    
167        @NotNull
168        @Override
169        public FieldVisitor newField(
170                @NotNull JvmDeclarationOrigin origin,
171                int access,
172                @NotNull String name,
173                @NotNull String desc,
174                @Nullable String signature,
175                @Nullable Object value
176        ) {
177            FieldVisitor internalVisitor = super.newField(origin, access, name, desc, signature, value);
178    
179            if (internalVisitor != EMPTY_FIELD_VISITOR) {
180                // If stub for field generated
181                markLastChild(origin);
182            }
183    
184            return internalVisitor;
185        }
186    
187        private void markLastChild(@NotNull JvmDeclarationOrigin origin) {
188            List children = v.getResult().getChildrenStubs();
189            StubBase last = (StubBase) children.get(children.size() - 1);
190    
191            LightElementOrigin oldOrigin = last.getUserData(ClsWrapperStubPsiFactory.ORIGIN);
192            if (oldOrigin != null) {
193                PsiElement originalElement = oldOrigin.getOriginalElement();
194                throw new IllegalStateException("Rewriting origin element: " +
195                                                (originalElement != null ? originalElement.getText() : null) + " for stub " + last.toString());
196            }
197    
198            last.putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightMemberOrigin(origin));
199        }
200    
201        @Override
202        public void done() {
203            if (!isPackageClass) {
204                StubElement pop = parentStack.pop();
205                assert pop == v.getResult() : "parentStack: got " + pop + ", expected " + v.getResult();
206            }
207            super.done();
208        }
209    }