View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.compiler;
20  
21  import javax.lang.model.SourceVersion;
22  
23  import java.io.Closeable;
24  import java.io.IOException;
25  import java.nio.file.Path;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.LinkedHashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.maven.api.PathType;
34  
35  /**
36   * Source files for a specific Java release. Instances of {@code SourcesForRelease} are created from
37   * a list of {@link SourceFile} after the sources have been filtered according include and exclude filters.
38   *
39   * @author Martin Desruisseaux
40   */
41  final class SourcesForRelease implements Closeable {
42      /**
43       * The release for this set of sources, or {@code null} if the user did not specified a release.
44       *
45       * @see #getReleaseString()
46       * @see SourceDirectory#release
47       */
48      final SourceVersion release;
49  
50      /**
51       * All source files. This is the union of all {@link SourceFile#file} for this {@linkplain #release}.
52       *
53       * @see SourceFile#file
54       */
55      final List<Path> files;
56  
57      /**
58       * All source directories that are part of this compilation unit, grouped by module names.
59       * The keys in the map are the module names, with the empty string standing for no module.
60       * Values are the union of all {@link SourceDirectory#root} for this {@linkplain #release}.
61       *
62       * @see SourceDirectory#root
63       */
64      final Map<String, Set<Path>> roots;
65  
66      /**
67       * The directories that contains a {@code module-info.java} file. If the set of source files
68       * is for a Java release different than the base release, or if it is for the test sources,
69       * then a non-empty map means that some modules overwrite {@code module-info.class}.
70       */
71      private final Map<SourceDirectory, ModuleInfoOverwrite> moduleInfos;
72  
73      /**
74       * Last directory added to the {@link #roots} map. This is a small optimization for reducing
75       * the number of accesses to the map. In most cases, only one element will be written there.
76       */
77      private SourceDirectory lastDirectoryAdded;
78  
79      /**
80       * Snapshot of {@link ToolExecutor#dependencies}.
81       * This information is saved in case a {@code target/javac.args} debug file needs to be written.
82       */
83      Map<PathType, Collection<Path>> dependencySnapshot;
84  
85      /**
86       * The output directory for the release. This is either the base output directory or a sub-directory
87       * in {@code META-INF/versions/}. This field is not used by this class, but made available for making
88       * easier to write the {@code target/javac.args} debug file.
89       */
90      Path outputForRelease;
91  
92      /**
93       * Creates an initially empty instance for the given Java release.
94       *
95       * @param release the release for this set of sources, or {@code null} if the user did not specified a release
96       */
97      SourcesForRelease(SourceVersion release) {
98          this.release = release;
99          files = new ArrayList<>();
100         roots = new LinkedHashMap<>();
101         moduleInfos = new LinkedHashMap<>();
102     }
103 
104     /**
105      * Returns the release as a string suitable for the {@code --release} compiler option.
106      *
107      * @return the release number as a string, or {@code null} if none
108      */
109     String getReleaseString() {
110         if (release == null) {
111             return null;
112         }
113         var version = release.name();
114         return version.substring(version.lastIndexOf('_') + 1);
115     }
116 
117     /**
118      * Adds the given source file to this collection of source files.
119      * The value of {@code source.directory.release}, if not null, must be equal to {@link #release}.
120      *
121      * @param source the source file to add.
122      */
123     void add(SourceFile source) {
124         var directory = source.directory;
125         if (lastDirectoryAdded != directory) {
126             lastDirectoryAdded = directory;
127             String moduleName = directory.moduleName;
128             if (moduleName == null || moduleName.isBlank()) {
129                 moduleName = "";
130             }
131             roots.get(moduleName).add(directory.root);
132             directory.getModuleInfo().ifPresent((path) -> moduleInfos.put(directory, null));
133         }
134         files.add(source.file);
135     }
136 
137     /**
138      * Restores the hidden {@code module-info.class} files to their original names.
139      */
140     @Override
141     public void close() throws IOException {
142         IOException error = null;
143         for (Map.Entry<SourceDirectory, ModuleInfoOverwrite> entry : moduleInfos.entrySet()) {
144             ModuleInfoOverwrite mo = entry.getValue();
145             if (mo != null) {
146                 entry.setValue(null);
147                 try {
148                     mo.restore();
149                 } catch (IOException e) {
150                     if (error == null) {
151                         error = e;
152                     } else {
153                         error.addSuppressed(e);
154                     }
155                 }
156             }
157         }
158         if (error != null) {
159             throw error;
160         }
161     }
162 
163     /**
164      * {@return a string representation for debugging purposes}
165      */
166     @Override
167     public String toString() {
168         var sb = new StringBuilder(getClass().getSimpleName()).append('[');
169         if (release != null) {
170             sb.append(release).append(": ");
171         }
172         return sb.append(files.size()).append(" files]").toString();
173     }
174 }