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