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.intellij.openapi.application.ApplicationManager;
020 import com.intellij.openapi.progress.ProcessCanceledException;
021 import com.intellij.openapi.util.io.FileUtil;
022 import com.intellij.openapi.vfs.VirtualFile;
023 import com.intellij.psi.PsiFile;
024 import com.intellij.util.PathUtil;
025 import org.jetbrains.annotations.NotNull;
026 import org.jetbrains.annotations.Nullable;
027 import org.jetbrains.asm4.AnnotationVisitor;
028 import org.jetbrains.asm4.Type;
029 import org.jetbrains.jet.codegen.context.CodegenContext;
030 import org.jetbrains.jet.codegen.context.FieldOwnerContext;
031 import org.jetbrains.jet.codegen.state.GenerationState;
032 import org.jetbrains.jet.descriptors.serialization.BitEncoding;
033 import org.jetbrains.jet.descriptors.serialization.DescriptorSerializer;
034 import org.jetbrains.jet.descriptors.serialization.PackageData;
035 import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
036 import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
037 import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils;
038 import org.jetbrains.jet.lang.psi.*;
039 import org.jetbrains.jet.lang.resolve.BindingContext;
040 import org.jetbrains.jet.lang.resolve.java.JvmAbi;
041 import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames;
042 import org.jetbrains.jet.lang.resolve.java.JvmClassName;
043 import org.jetbrains.jet.lang.resolve.name.FqName;
044 import org.jetbrains.jet.lang.resolve.name.Name;
045
046 import java.util.ArrayList;
047 import java.util.Collection;
048 import java.util.List;
049
050 import static org.jetbrains.asm4.Opcodes.*;
051 import static org.jetbrains.jet.codegen.AsmUtil.asmDescByFqNameWithoutInnerClasses;
052 import static org.jetbrains.jet.codegen.AsmUtil.asmTypeByFqNameWithoutInnerClasses;
053 import static org.jetbrains.jet.descriptors.serialization.NameSerializationUtil.createNameResolver;
054 import static org.jetbrains.jet.lang.resolve.java.PackageClassUtils.getPackageClassFqName;
055
056 public class NamespaceCodegen extends MemberCodegen {
057 @NotNull
058 private final ClassBuilderOnDemand v;
059
060 @NotNull
061 private final FqName name;
062
063 @NotNull
064 private final Collection<JetFile> files;
065 private final NamespaceDescriptor descriptor;
066
067 public NamespaceCodegen(
068 @NotNull ClassBuilderOnDemand v,
069 @NotNull final FqName fqName,
070 @NotNull GenerationState state,
071 @NotNull Collection<JetFile> namespaceFiles
072 ) {
073 super(state, null);
074 checkAllFilesHaveSameNamespace(namespaceFiles);
075
076 this.v = v;
077 name = fqName;
078 this.files = namespaceFiles;
079
080 descriptor = state.getBindingContext().get(BindingContext.FQNAME_TO_NAMESPACE_DESCRIPTOR, name);
081 assert descriptor != null : "No namespace found for FQ name " + name;
082
083 final PsiFile sourceFile = namespaceFiles.size() == 1 ? namespaceFiles.iterator().next().getContainingFile() : null;
084
085 v.addOptionalDeclaration(new ClassBuilderOnDemand.ClassBuilderCallback() {
086 @Override
087 public void doSomething(@NotNull ClassBuilder v) {
088 v.defineClass(sourceFile, V1_6,
089 ACC_PUBLIC | ACC_FINAL,
090 JvmClassName.byFqNameWithoutInnerClasses(getPackageClassFqName(fqName)).getInternalName(),
091 null,
092 //"jet/lang/Namespace",
093 "java/lang/Object",
094 new String[0]
095 );
096 //We don't generate any source information for namespace with multiple files
097 if (sourceFile != null) {
098 v.visitSource(sourceFile.getName(), null);
099 }
100 }
101 });
102 }
103
104 public void generate(@NotNull CompilationErrorHandler errorHandler) {
105 List<JvmSerializationBindings> bindings = new ArrayList<JvmSerializationBindings>(files.size() + 1);
106 boolean shouldGeneratePackageClass = shouldGenerateNSClass(files);
107 if (shouldGeneratePackageClass) {
108 bindings.add(v.getClassBuilder().getSerializationBindings());
109 }
110
111 for (JetFile file : files) {
112 try {
113 ClassBuilder builder = generate(file);
114 if (builder != null) {
115 bindings.add(builder.getSerializationBindings());
116 }
117 }
118 catch (ProcessCanceledException e) {
119 throw e;
120 }
121 catch (Throwable e) {
122 VirtualFile vFile = file.getVirtualFile();
123 errorHandler.reportException(e, vFile == null ? "no file" : vFile.getUrl());
124 DiagnosticUtils.throwIfRunningOnServer(e);
125 if (ApplicationManager.getApplication().isInternal()) {
126 //noinspection CallToPrintStackTrace
127 e.printStackTrace();
128 }
129 }
130 }
131
132 if (shouldGeneratePackageClass) {
133 writeKotlinPackageAnnotationIfNeeded(JvmSerializationBindings.union(bindings));
134 }
135
136 assert v.isActivated() == shouldGeneratePackageClass :
137 "Different algorithms for generating namespace class and for heuristics for: " + name.asString();
138 }
139
140 private void writeKotlinPackageAnnotationIfNeeded(@NotNull JvmSerializationBindings bindings) {
141 if (state.getClassBuilderMode() != ClassBuilderMode.FULL) {
142 return;
143 }
144
145 for (JetFile file : files) {
146 if (file.isScript()) return;
147 }
148
149 DescriptorSerializer serializer = new DescriptorSerializer(new JavaSerializerExtension(bindings));
150 ProtoBuf.Package packageProto = serializer.packageProto(descriptor).build();
151
152 if (packageProto.getMemberCount() == 0) return;
153
154 PackageData data = new PackageData(createNameResolver(serializer.getNameTable()), packageProto);
155
156 AnnotationVisitor av =
157 v.getClassBuilder().newAnnotation(asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_PACKAGE), true);
158 av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION);
159 AnnotationVisitor array = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME);
160 for (String string : BitEncoding.encodeBytes(data.toBytes())) {
161 array.visit(null, string);
162 }
163 array.visitEnd();
164 av.visitEnd();
165 }
166
167 @Nullable
168 private ClassBuilder generate(@NotNull JetFile file) {
169 boolean generateSrcClass = false;
170 FieldOwnerContext packageFragmentContext = CodegenContext.STATIC.intoNamespace(descriptor);
171
172 for (JetDeclaration declaration : file.getDeclarations()) {
173 if (declaration instanceof JetProperty || declaration instanceof JetNamedFunction) {
174 generateSrcClass = true;
175 }
176 else if (declaration instanceof JetClassOrObject) {
177 if (state.isGenerateDeclaredClasses()) {
178 generateClassOrObject((JetClassOrObject) declaration);
179 }
180 }
181 else if (declaration instanceof JetScript) {
182 ScriptCodegen.createScriptCodegen((JetScript) declaration, state, packageFragmentContext).generate();
183 }
184 }
185
186 if (!generateSrcClass) return null;
187
188 Type packageFragmentType = getNamespacePartType(getPackageClassFqName(name), file.getVirtualFile());
189 ClassBuilder builder = state.getFactory().forPackageFragment(packageFragmentType, file);
190
191 new NamespacePartCodegen(builder, file, packageFragmentType, packageFragmentContext, state).generate();
192
193 FieldOwnerContext namespaceFacade = CodegenContext.STATIC.intoNamespaceFacade(packageFragmentType, descriptor);
194
195 for (JetDeclaration declaration : file.getDeclarations()) {
196 if (declaration instanceof JetNamedFunction || declaration instanceof JetProperty) {
197 genFunctionOrProperty(namespaceFacade, (JetTypeParameterListOwner) declaration, v.getClassBuilder());
198 }
199 }
200
201 return builder;
202 }
203
204 public void generateClassOrObject(@NotNull JetClassOrObject classOrObject) {
205 CodegenContext context = CodegenContext.STATIC.intoNamespace(descriptor);
206 genClassOrObject(context, classOrObject);
207 }
208
209 /**
210 * @param namespaceFiles all files should have same package name
211 * @return
212 */
213 public static boolean shouldGenerateNSClass(Collection<JetFile> namespaceFiles) {
214 checkAllFilesHaveSameNamespace(namespaceFiles);
215
216 for (JetFile file : namespaceFiles) {
217 for (JetDeclaration declaration : file.getDeclarations()) {
218 if (declaration instanceof JetProperty ||
219 declaration instanceof JetNamedFunction ||
220 declaration instanceof JetObjectDeclaration) {
221 return true;
222 }
223 }
224 }
225
226 return false;
227 }
228
229 private static void checkAllFilesHaveSameNamespace(Collection<JetFile> namespaceFiles) {
230 FqName commonFqName = null;
231 for (JetFile file : namespaceFiles) {
232 FqName fqName = JetPsiUtil.getFQName(file);
233 if (commonFqName != null) {
234 if (!commonFqName.equals(fqName)) {
235 throw new IllegalArgumentException("All files should have same package name");
236 }
237 }
238 else {
239 commonFqName = JetPsiUtil.getFQName(file);
240 }
241 }
242 }
243
244 public void done() {
245 v.done();
246 }
247
248 @NotNull
249 public static Type getNamespacePartType(@NotNull FqName facadeFqName, @NotNull VirtualFile file) {
250 String fileName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(file.getName()));
251
252 // path hashCode to prevent same name / different path collision
253 String srcName = facadeFqName.shortName().asString() + "-" + replaceSpecialSymbols(fileName) + "-" + Integer.toHexString(
254 CodegenUtil.getPathHashCode(file));
255
256 FqName srcFqName = facadeFqName.parent().child(Name.identifier(srcName));
257
258 return asmTypeByFqNameWithoutInnerClasses(srcFqName);
259 }
260
261 @NotNull
262 private static String replaceSpecialSymbols(@NotNull String str) {
263 return str.replace('.', '_');
264 }
265
266 @NotNull
267 public static String getNamespacePartInternalName(@NotNull JetFile file) {
268 FqName packageFqName = JetPsiUtil.getFQName(file);
269 return getNamespacePartType(getPackageClassFqName(packageFqName), file.getVirtualFile()).getInternalName();
270 }
271 }