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 }