001 /*
002 * Copyright 2010-2013 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.jet.codegen;
018
019 import com.google.common.collect.Lists;
020 import com.intellij.openapi.application.ApplicationManager;
021 import com.intellij.openapi.progress.ProcessCanceledException;
022 import com.intellij.openapi.util.io.FileUtil;
023 import com.intellij.openapi.vfs.VirtualFile;
024 import com.intellij.psi.PsiFile;
025 import com.intellij.util.PathUtil;
026 import org.jetbrains.annotations.NotNull;
027 import org.jetbrains.asm4.AnnotationVisitor;
028 import org.jetbrains.asm4.MethodVisitor;
029 import org.jetbrains.asm4.Type;
030 import org.jetbrains.jet.codegen.context.CodegenContext;
031 import org.jetbrains.jet.codegen.context.FieldOwnerContext;
032 import org.jetbrains.jet.codegen.state.GenerationState;
033 import org.jetbrains.jet.lang.descriptors.*;
034 import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
035 import org.jetbrains.jet.lang.descriptors.impl.SimpleFunctionDescriptorImpl;
036 import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils;
037 import org.jetbrains.jet.lang.psi.*;
038 import org.jetbrains.jet.lang.resolve.BindingContext;
039 import org.jetbrains.jet.lang.resolve.java.JvmAbi;
040 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
041 import org.jetbrains.jet.lang.resolve.java.JvmStdlibNames;
042 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
043 import org.jetbrains.jet.lang.resolve.name.FqName;
044 import org.jetbrains.jet.lang.resolve.name.Name;
045
046 import java.util.Collection;
047 import java.util.Collections;
048 import java.util.List;
049
050 import static org.jetbrains.asm4.Opcodes.*;
051
052 public class NamespaceCodegen extends MemberCodegen {
053 @NotNull
054 private final ClassBuilderOnDemand v;
055 @NotNull private final FqName name;
056 private final Collection<JetFile> files;
057
058 public NamespaceCodegen(
059 @NotNull ClassBuilderOnDemand v,
060 @NotNull final FqName fqName,
061 GenerationState state,
062 Collection<JetFile> namespaceFiles
063 ) {
064 super(state, null);
065 checkAllFilesHaveSameNamespace(namespaceFiles);
066
067 this.v = v;
068 name = fqName;
069 this.files = namespaceFiles;
070
071 final PsiFile sourceFile = namespaceFiles.size() == 1 ? namespaceFiles.iterator().next().getContainingFile() : null;
072
073 v.addOptionalDeclaration(new ClassBuilderOnDemand.ClassBuilderCallback() {
074 @Override
075 public void doSomething(@NotNull ClassBuilder v) {
076 v.defineClass(sourceFile, V1_6,
077 ACC_PUBLIC | ACC_FINAL,
078 getJVMClassNameForKotlinNs(fqName).getInternalName(),
079 null,
080 //"jet/lang/Namespace",
081 "java/lang/Object",
082 new String[0]
083 );
084 //We don't generate any source information for namespace with multiple files
085 if (sourceFile != null) {
086 v.visitSource(sourceFile.getName(), null);
087 }
088 }
089 });
090 }
091
092 public void generate(CompilationErrorHandler errorHandler) {
093 if (shouldGenerateNSClass(files)) {
094 AnnotationVisitor packageClassAnnotation = v.getClassBuilder().newAnnotation(JvmStdlibNames.JET_PACKAGE_CLASS.getDescriptor(), true);
095 packageClassAnnotation.visit(JvmStdlibNames.ABI_VERSION_NAME, JvmAbi.VERSION);
096 packageClassAnnotation.visitEnd();
097 }
098
099 for (JetFile file : files) {
100 VirtualFile vFile = file.getVirtualFile();
101 try {
102 generate(file);
103 }
104 catch (ProcessCanceledException e) {
105 throw e;
106 }
107 catch (Throwable e) {
108 if (errorHandler != null) errorHandler.reportException(e, vFile == null ? "no file" : vFile.getUrl());
109 DiagnosticUtils.throwIfRunningOnServer(e);
110 if (ApplicationManager.getApplication().isInternal()) {
111 //noinspection CallToPrintStackTrace
112 e.printStackTrace();
113 }
114 }
115 }
116
117 assert v.isActivated() == shouldGenerateNSClass(files) : "Different algorithms for generating namespace class and for heuristics";
118 }
119
120 private void generate(JetFile file) {
121 NamespaceDescriptor descriptor = state.getBindingContext().get(BindingContext.FILE_TO_NAMESPACE, file);
122 assert descriptor != null : "No namespace found for file " + file + " declared package: " + file.getPackageName();
123 int countOfDeclarationsInSrcClass = 0;
124 for (JetDeclaration declaration : file.getDeclarations()) {
125 if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
126 countOfDeclarationsInSrcClass++;
127 }
128 else if (declaration instanceof JetClassOrObject) {
129 if (state.isGenerateDeclaredClasses()) {
130 generateClassOrObject(descriptor, (JetClassOrObject) declaration);
131 }
132 }
133 else if (declaration instanceof JetScript) {
134 state.getScriptCodegen().generate((JetScript) declaration);
135 }
136 }
137
138 if (countOfDeclarationsInSrcClass > 0) {
139 String namespaceInternalName = JvmClassName.byFqNameWithoutInnerClasses(
140 PackageClassUtils.getPackageClassFqName(name)).getInternalName();
141 String className = getMultiFileNamespaceInternalName(namespaceInternalName, file);
142 ClassBuilder builder = state.getFactory().forNamespacepart(className, file);
143
144 builder.defineClass(file, V1_6,
145 ACC_PUBLIC | ACC_FINAL,
146 className,
147 null,
148 //"jet/lang/Namespace",
149 "java/lang/Object",
150 new String[0]
151 );
152 builder.visitSource(file.getName(), null);
153
154 FieldOwnerContext nameSpaceContext =
155 CodegenContext.STATIC.intoNamespace(descriptor);
156
157 FieldOwnerContext nameSpacePart =
158 CodegenContext.STATIC.intoNamespacePart(className, descriptor);
159
160 for (JetDeclaration declaration : file.getDeclarations()) {
161 if (declaration instanceof JetNamedFunction || declaration instanceof JetProperty) {
162 genFunctionOrProperty(nameSpaceContext, (JetTypeParameterListOwner) declaration, builder);
163 genFunctionOrProperty(nameSpacePart, (JetTypeParameterListOwner) declaration, v.getClassBuilder());
164 }
165 }
166
167 generateStaticInitializers(descriptor, builder, file, nameSpaceContext);
168
169 builder.done();
170 }
171 }
172
173 public void generateClassOrObject(@NotNull NamespaceDescriptor descriptor, @NotNull JetClassOrObject classOrObject) {
174 CodegenContext context = CodegenContext.STATIC.intoNamespace(descriptor);
175 genClassOrObject(context, classOrObject);
176 }
177
178 /**
179 * @param namespaceFiles all files should have same package name
180 * @return
181 */
182 public static boolean shouldGenerateNSClass(Collection<JetFile> namespaceFiles) {
183 checkAllFilesHaveSameNamespace(namespaceFiles);
184
185 for (JetFile file : namespaceFiles) {
186 for (JetDeclaration declaration : file.getDeclarations()) {
187 if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
188 return true;
189 }
190 }
191 }
192
193 return false;
194 }
195
196 private static void checkAllFilesHaveSameNamespace(Collection<JetFile> namespaceFiles) {
197 FqName commonFqName = null;
198 for (JetFile file : namespaceFiles) {
199 FqName fqName = JetPsiUtil.getFQName(file);
200 if (commonFqName != null) {
201 if (!commonFqName.equals(fqName)) {
202 throw new IllegalArgumentException("All files should have same package name");
203 }
204 }
205 else {
206 commonFqName = JetPsiUtil.getFQName(file);
207 }
208 }
209 }
210
211 private void generateStaticInitializers(
212 NamespaceDescriptor descriptor,
213 @NotNull ClassBuilder builder,
214 @NotNull JetFile file,
215 @NotNull FieldOwnerContext context
216 ) {
217 List<JetProperty> properties = collectPropertiesToInitialize(file);
218 if (properties.isEmpty()) return;
219
220 MethodVisitor mv = builder.newMethod(file, ACC_STATIC, "<clinit>", "()V", null, null);
221 if (state.getClassBuilderMode() == ClassBuilderMode.FULL) {
222 mv.visitCode();
223
224 FrameMap frameMap = new FrameMap();
225
226 SimpleFunctionDescriptorImpl clInit =
227 new SimpleFunctionDescriptorImpl(descriptor, Collections.<AnnotationDescriptor>emptyList(),
228 Name.special("<clinit>"),
229 CallableMemberDescriptor.Kind.SYNTHESIZED);
230 clInit.initialize(null, null, Collections.<TypeParameterDescriptor>emptyList(),
231 Collections.<ValueParameterDescriptor>emptyList(), null, null, Visibilities.PRIVATE, false);
232
233 ExpressionCodegen codegen = new ExpressionCodegen(mv, frameMap, Type.VOID_TYPE, context.intoFunction(clInit), state);
234
235 for (JetDeclaration declaration : properties) {
236 ImplementationBodyCodegen.
237 initializeProperty(codegen, state.getBindingContext(), (JetProperty) declaration);
238 }
239
240 mv.visitInsn(RETURN);
241 FunctionCodegen.endVisit(mv, "static initializer for namespace", file);
242 mv.visitEnd();
243 }
244 }
245
246 @NotNull
247 private List<JetProperty> collectPropertiesToInitialize(@NotNull JetFile file) {
248 List<JetProperty> result = Lists.newArrayList();
249 for (JetDeclaration declaration : file.getDeclarations()) {
250 if (declaration instanceof JetProperty &&
251 ImplementationBodyCodegen.shouldInitializeProperty((JetProperty) declaration, typeMapper)) {
252 result.add((JetProperty) declaration);
253 }
254 }
255 return result;
256 }
257
258 public void done() {
259 v.done();
260 }
261
262 @NotNull
263 public static JvmClassName getJVMClassNameForKotlinNs(@NotNull FqName fqName) {
264 String packageClassName = PackageClassUtils.getPackageClassName(fqName);
265 if (fqName.isRoot()) {
266 return JvmClassName.byInternalName(packageClassName);
267 }
268
269 return JvmClassName.byFqNameWithoutInnerClasses(fqName.child(Name.identifier(packageClassName)));
270 }
271
272 @NotNull
273 private static String getMultiFileNamespaceInternalName(@NotNull String namespaceInternalName, @NotNull PsiFile file) {
274 String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
275
276 // path hashCode to prevent same name / different path collision
277 return namespaceInternalName + "$src$" + replaceSpecialSymbols(fileName) + "$" + Integer.toHexString(
278 CodegenUtil.getPathHashCode(file));
279 }
280
281 private static String replaceSpecialSymbols(@NotNull String str) {
282 return str.replace('.', '_');
283 }
284
285 @NotNull
286 public static String getNamespacePartInternalName(@NotNull JetFile file) {
287 FqName fqName = JetPsiUtil.getFQName(file);
288 JvmClassName namespaceJvmClassName = NamespaceCodegen.getJVMClassNameForKotlinNs(fqName);
289 String namespaceInternalName = namespaceJvmClassName.getInternalName();
290 return NamespaceCodegen.getMultiFileNamespaceInternalName(namespaceInternalName, file);
291 }
292 }