Cleanup: NotNull/Nullable
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / ant / ModuleChunkClasspath.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.compiler.ant;
17
18 import com.intellij.compiler.ant.taskdefs.Path;
19 import com.intellij.compiler.ant.taskdefs.PathElement;
20 import com.intellij.compiler.ant.taskdefs.PathRef;
21 import com.intellij.openapi.module.Module;
22 import com.intellij.openapi.project.ex.ProjectEx;
23 import com.intellij.openapi.roots.*;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.openapi.vfs.JarFileSystem;
26 import com.intellij.openapi.vfs.VirtualFileManager;
27 import com.intellij.util.ArrayUtil;
28 import com.intellij.util.containers.ContainerUtil;
29 import com.intellij.util.containers.OrderedSet;
30
31 import java.io.File;
32 import java.util.HashSet;
33 import java.util.Set;
34
35 /**
36  * Generator of module chunk classspath. The class used to generate both runtime and compile time classpaths.
37  *
38  * @author Eugene Zhuravlev
39  */
40 public class ModuleChunkClasspath extends Path {
41
42   /**
43    * A constructor
44    *
45    * @param chunk                    a chunk to process
46    * @param genOptions               a generation options
47    * @param generateRuntimeClasspath if true, runtime classpath is being generated. Otherwise a compile time classpath is constructed
48    * @param generateTestClasspath    if true, a test classpath is generated.
49    */
50   public ModuleChunkClasspath(final ModuleChunk chunk,
51                               final GenerationOptions genOptions,
52                               final boolean generateRuntimeClasspath,
53                               final boolean generateTestClasspath) {
54     super(generateClasspathName(chunk, generateRuntimeClasspath, generateTestClasspath));
55
56     final OrderedSet<ClasspathItem> pathItems =
57       new OrderedSet<>();
58     final String moduleChunkBasedirProperty = BuildProperties.getModuleChunkBasedirProperty(chunk);
59     final Module[] modules = chunk.getModules();
60     // processed chunks (used only in runtime classpath), every chunk is referenced exactly once
61     final Set<ModuleChunk> processedChunks = new HashSet<>();
62     // pocessed modules
63     final Set<Module> processedModules = new HashSet<>();
64     for (final Module module : modules) {
65       new Object() {
66         /**
67          * Process the module. The logic is different for compile-time case and runtime case.
68          * In the case of runtime, only directly referenced objects are included in classpath.
69          * Indirectly referenced are
70          *
71          * @param module a module to process.
72          * @param dependencyLevel is increased with every of recursion.
73          * @param isModuleExported if true the module is exported from the previous level
74          */
75         public void processModule(final Module module, final int dependencyLevel, final boolean isModuleExported) {
76           if (processedModules.contains(module)) {
77             // the module is already processed, nothing should be done
78             return;
79           }
80           if (dependencyLevel > 1 && !isModuleExported && !(genOptions.inlineRuntimeClasspath && generateRuntimeClasspath)) {
81             // the module is not in exports and it is not directly included skip it in the case of library pathgeneration
82             return;
83           }
84           processedModules.add(module);
85           final ProjectEx project = (ProjectEx)chunk.getProject();
86           final File baseDir = BuildProperties.getProjectBaseDir(project);
87           OrderEnumerator enumerator = ModuleRootManager.getInstance(module).orderEntries();
88           if (generateRuntimeClasspath) {
89             enumerator = enumerator.runtimeOnly();
90           }
91           else {
92             enumerator = enumerator.compileOnly();
93             if (!generateTestClasspath && (dependencyLevel == 0 || chunk.contains(module))) {
94               // this is the entry for outpath of the currently processed module
95               // the root module is never included
96               enumerator = enumerator.withoutModuleSourceEntries();
97             }
98           }
99           if (!generateTestClasspath) {
100             enumerator = enumerator.productionOnly();
101           }
102           enumerator.forEach(orderEntry -> {
103             if (!orderEntry.isValid()) {
104               return true;
105             }
106
107             if (!generateRuntimeClasspath &&
108                 !(orderEntry instanceof ModuleOrderEntry) &&
109                 !(orderEntry instanceof ModuleSourceOrderEntry)) {
110               // needed for compilation classpath only
111               final boolean isExported = (orderEntry instanceof ExportableOrderEntry) && ((ExportableOrderEntry)orderEntry).isExported();
112               if (dependencyLevel > 0 && !isExported) {
113                 // non-exported dependencies are excluded and not processed
114                 return true;
115               }
116             }
117
118             if (orderEntry instanceof JdkOrderEntry) {
119               if (genOptions.forceTargetJdk && !generateRuntimeClasspath) {
120                 pathItems
121                   .add(new PathRefItem(BuildProperties.propertyRef(BuildProperties.getModuleChunkJdkClasspathProperty(chunk.getName()))));
122               }
123             }
124             else if (orderEntry instanceof ModuleOrderEntry) {
125               final ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)orderEntry;
126               final Module dependentModule = moduleOrderEntry.getModule();
127               if (!chunk.contains(dependentModule)) {
128                 if (generateRuntimeClasspath && !genOptions.inlineRuntimeClasspath) {
129                   // in case of runtime classpath, just an referenced to corresponding classpath is created
130                   final ModuleChunk depChunk = genOptions.getChunkByModule(dependentModule);
131                   if (!processedChunks.contains(depChunk)) {
132                     // chunk references are included in the runtime classpath only once
133                     processedChunks.add(depChunk);
134                     String property = generateTestClasspath ? BuildProperties.getTestRuntimeClasspathProperty(depChunk.getName())
135                                                             : BuildProperties.getRuntimeClasspathProperty(depChunk.getName());
136                     pathItems.add(new PathRefItem(property));
137                   }
138                 }
139                 else {
140                   // in case of compile classpath or inlined runtime classpath,
141                   // the referenced module is processed recursively
142                   processModule(dependentModule, dependencyLevel + 1, moduleOrderEntry.isExported());
143                 }
144               }
145             }
146             else if (orderEntry instanceof LibraryOrderEntry) {
147               final LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)orderEntry;
148               final String libraryName = libraryOrderEntry.getLibraryName();
149               if (((LibraryOrderEntry)orderEntry).isModuleLevel()) {
150                 CompositeGenerator gen = new CompositeGenerator();
151                 gen.setHasLeadingNewline(false);
152                 LibraryDefinitionsGeneratorFactory.genLibraryContent(project, genOptions, libraryOrderEntry.getLibrary(), baseDir, gen);
153                 pathItems.add(new GeneratorItem(libraryName, gen));
154               }
155               else {
156                 pathItems.add(new PathRefItem(BuildProperties.getLibraryPathId(libraryName)));
157               }
158             }
159             else if (orderEntry instanceof ModuleSourceOrderEntry) {
160               // Module source entry?
161               for (String url : getCompilationClasses(module, ((GenerationOptionsImpl)genOptions), generateRuntimeClasspath,
162                                                       generateTestClasspath, dependencyLevel == 0)) {
163                 url = StringUtil.trimEnd(url, JarFileSystem.JAR_SEPARATOR);
164                 final String propertyRef = genOptions.getPropertyRefForUrl(url);
165                 if (propertyRef != null) {
166                   pathItems.add(new PathElementItem(propertyRef));
167                 }
168                 else {
169                   final String path = VirtualFileManager.extractPath(url);
170                   pathItems.add(new PathElementItem(
171                     GenerationUtils.toRelativePath(path, chunk.getBaseDir(), moduleChunkBasedirProperty, genOptions)));
172                 }
173               }
174             }
175             else {
176               // Unknown order entry type. If it is actually encountered, extension point should be implemented
177               pathItems.add(new GeneratorItem(orderEntry.getClass().getName(),
178                                               new Comment("Unknown OrderEntryType: " + orderEntry.getClass().getName())));
179             }
180             return true;
181           });
182         }
183       }.processModule(module, 0, false);
184     }
185     // convert path items to generators
186     for (final ClasspathItem pathItem : pathItems) {
187       add(pathItem.toGenerator());
188     }
189   }
190
191   /**
192    * Generate classpath name
193    *
194    * @param chunk                    a chunk
195    * @param generateRuntimeClasspath
196    * @param generateTestClasspath
197    * @return a name for the classpath
198    */
199   private static String generateClasspathName(ModuleChunk chunk, boolean generateRuntimeClasspath, boolean generateTestClasspath) {
200     if (generateTestClasspath) {
201       return generateRuntimeClasspath
202              ? BuildProperties.getTestRuntimeClasspathProperty(chunk.getName())
203              : BuildProperties.getTestClasspathProperty(chunk.getName());
204     }
205     else {
206       return generateRuntimeClasspath
207              ? BuildProperties.getRuntimeClasspathProperty(chunk.getName())
208              : BuildProperties.getClasspathProperty(chunk.getName());
209     }
210   }
211
212   private static String[] getCompilationClasses(final Module module,
213                                                 final GenerationOptionsImpl options,
214                                                 final boolean forRuntime,
215                                                 final boolean forTest,
216                                                 final boolean firstLevel) {
217     final CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module);
218     if (extension == null) return ArrayUtil.EMPTY_STRING_ARRAY;
219
220     if (!forRuntime) {
221       if (forTest) {
222         return extension.getOutputRootUrls(!firstLevel);
223       }
224       else {
225         return firstLevel ? ArrayUtil.EMPTY_STRING_ARRAY : extension.getOutputRootUrls(false);
226       }
227     }
228     final Set<String> jdkUrls = options.getAllJdkUrls();
229
230     final OrderedSet<String> urls = new OrderedSet<>();
231     ContainerUtil.addAll(urls, extension.getOutputRootUrls(forTest));
232     urls.removeAll(jdkUrls);
233     return ArrayUtil.toStringArray(urls);
234   }
235
236   /**
237    * The base class for an item in the class path. Instances of the subclasses are used instead
238    * of generators when building the class path content. The subclasses implement {@link Object#equals(Object)}
239    * and {@link Object#hashCode()} methods in order to eliminate duplicates when building classpath.
240    */
241   private abstract static class ClasspathItem {
242     /**
243      * primary reference or path of the element
244      */
245     protected final String myValue;
246
247     /**
248      * A constructor
249      *
250      * @param value primary value of the element
251      */
252     ClasspathItem(String value) {
253       myValue = value;
254     }
255
256     /**
257      * @return a generator for path elements.
258      */
259     public abstract Generator toGenerator();
260   }
261
262   /**
263    * Class path item that directly embeds generator
264    */
265   private static class GeneratorItem extends ClasspathItem {
266     /**
267      * An embedded generator
268      */
269     final Generator myGenerator;
270
271     /**
272      * A constructor
273      *
274      * @param value     primary value of the element
275      * @param generator a generator to use
276      */
277     GeneratorItem(String value, final Generator generator) {
278       super(value);
279       myGenerator = generator;
280     }
281
282     @Override
283     public Generator toGenerator() {
284       return myGenerator;
285     }
286   }
287
288   /**
289    * This path element directly references some location.
290    */
291   private static class PathElementItem extends ClasspathItem {
292     /**
293      * A constructor
294      *
295      * @param value a referenced location
296      */
297     PathElementItem(String value) {
298       super(value);
299     }
300
301     @Override
302     public Generator toGenerator() {
303       return new PathElement(myValue);
304     }
305
306     @Override
307     public boolean equals(Object o) {
308       if (this == o) return true;
309       if (!(o instanceof PathElementItem)) return false;
310
311       final PathElementItem pathElementItem = (PathElementItem)o;
312
313       if (!myValue.equals(pathElementItem.myValue)) return false;
314
315       return true;
316     }
317
318     @Override
319     public int hashCode() {
320       return myValue.hashCode();
321     }
322   }
323
324   /**
325    * This path element references a path
326    */
327   private static class PathRefItem extends ClasspathItem {
328     /**
329      * A constructor
330      *
331      * @param value an indentifier of referenced classpath
332      */
333     PathRefItem(String value) {
334       super(value);
335     }
336
337     @Override
338     public Generator toGenerator() {
339       return new PathRef(myValue);
340     }
341
342     @Override
343     public boolean equals(Object o) {
344       if (this == o) return true;
345       if (!(o instanceof PathRefItem)) return false;
346
347       final PathRefItem pathRefItem = (PathRefItem)o;
348
349       if (!myValue.equals(pathRefItem.myValue)) return false;
350
351       return true;
352     }
353
354     @Override
355     public int hashCode() {
356       return myValue.hashCode();
357     }
358   }
359 }