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 java.nio.file.Path;
22  import java.nio.file.attribute.BasicFileAttributes;
23  
24  /**
25   * A single source file, associated with the root directory from which it belong.
26   * This class contains also the output file, because this information is needed
27   * for determining whether a source file need to be recompiled.
28   *
29   * @author Martin Desruisseaux
30   */
31  final class SourceFile {
32      /**
33       * The root directory which was walked for obtaining this file.
34       */
35      final SourceDirectory directory;
36  
37      /**
38       * The source file found by walking under the directory.
39       * This path is already resolved relative to {@link SourceDirectory#root}.
40       */
41      final Path file;
42  
43      /**
44       * The time this file object was last modified, in milliseconds since January 1, 1970.
45       */
46      final long lastModified;
47  
48      /**
49       * Whether this source has been flagged as new or modified since the last build.
50       *
51       * @see IncrementalBuildHelper#inputFileTreeChanges
52       */
53      boolean isNewOrModified;
54  
55      /**
56       * Whether to ignore this file for incremental build calculation.
57       * This flag is set to {@code true} if this file matches a filter
58       * specified by {@link AbstractCompilerMojo#getIncrementalExcludes()}.
59       *
60       * <p>Note that a value of {@code true} should not prevent the {@link #isNewOrModified} flag to be
61       * set to {@code true} if a modification is detected, because we want this file to be included in
62       * compilation unit if a compilation is decided for another reason than a change of this file.</p>
63       *
64       * @see AbstractCompilerMojo#getIncrementalExcludes()
65       */
66      final boolean ignoreModification;
67  
68      /**
69       * The path of the {@code .class} file, created when first requested.
70       *
71       * @see #getOutputFile()
72       */
73      private Path outputFile;
74  
75      /**
76       * Creates a new source file.
77       *
78       * @param directory the root directory where the file come from
79       * @param file a source file found by walking under the directory
80       * @param attrs the source file attributes
81       * @param ignoreModification whether to ignore this file for incremental build calculation
82       */
83      SourceFile(SourceDirectory directory, Path file, BasicFileAttributes attrs, boolean ignoreModification) {
84          this.directory = directory;
85          this.file = file;
86          this.lastModified = attrs.lastModifiedTime().toMillis();
87          this.ignoreModification = ignoreModification;
88          directory.visit(file);
89      }
90  
91      /**
92       * {@return whether the output file is the same as the one that we would infer from heuristic rules}
93       *
94       * <p>TODO: this is not yet implemented. We need to clarify how to get the output file information
95       * from the compiler, maybe via the {@link javax.tools.JavaFileManager#getFileForOutput} method.
96       * Then, {@link #getOutputFile} should compare that value with the inferred one and set a flag.</p>
97       */
98      boolean isStandardOutputFile() {
99          // The constants below must match the ones in `IncrementalBuild.SourceInfo`.
100         return SourceDirectory.JAVA_FILE_SUFFIX.equals(directory.fileKind.extension)
101                 && SourceDirectory.CLASS_FILE_SUFFIX.equals(directory.outputFileKind.extension);
102     }
103 
104     /**
105      * Returns the file resulting from the compilation of this source file. If the output file has been
106      * {@linkplain javax.tools.JavaFileManager#getFileForOutput obtained from the compiler}, that value
107      * if returned. Otherwise, output file is inferred using {@linkplain #toOutputFile heuristic rules}.
108      *
109      * @return path to the output file
110      */
111     Path getOutputFile() {
112         if (outputFile == null) {
113             outputFile = toOutputFile(
114                     directory.root,
115                     directory.getOutputDirectory(),
116                     file,
117                     directory.fileKind.extension,
118                     directory.outputFileKind.extension);
119             /*
120              * TODO: compare with the file given by the compiler (if we can get that information)
121              * and set a `isStandardOutputFile` flag with the comparison result.
122              */
123         }
124         return outputFile;
125     }
126 
127     /**
128      * Infers the path to the output file using heuristic rules.
129      * If the extension of the file is the one of {@linkplain SourceDirectory#fileKind source file kind}
130      * (usually {@code ".java"}), then it is replaced by the extension specified in {@code outputFileKind}.
131      * Otherwise the extension is left unmodified. Then, the path is made relative to the output directory.
132      *
133      * @param sourceDirectory root directory of the source file
134      * @param outputDirectory output directory of the compiled file
135      * @param file path to the source file
136      * @param extension expected extension of the source file, leading dot included
137      * @param outext extension of the output file, leading dot included
138      * @return path to the target file
139      */
140     static Path toOutputFile(Path sourceDirectory, Path outputDirectory, Path file, String extension, String outext) {
141         Path output = sourceDirectory.relativize(file);
142         String filename = file.getFileName().toString();
143         if (filename.endsWith(extension)) {
144             filename = filename.substring(0, filename.length() - extension.length());
145             filename = filename.concat(outext);
146             output = output.resolveSibling(filename);
147         }
148         return outputDirectory.resolve(output);
149     }
150 
151     /**
152      * Compares the given object with this source file for equality.
153      * This method compares only the file path. Metadata such as last modification time are ignored.
154      *
155      * @param obj the object to compare
156      * @return whether the two objects have the same path and attributes
157      */
158     @Override
159     public boolean equals(Object obj) {
160         if (obj instanceof SourceFile other) {
161             return file.equals(other.file) && directory.equals(other.directory);
162         }
163         return false;
164     }
165 
166     /**
167      * {@return a hash code value for this file}
168      */
169     @Override
170     public int hashCode() {
171         return directory.hashCode() + 7 * file.hashCode();
172     }
173 
174     /**
175      * {@return a string representation of this source file for debugging purposes}
176      * This string representation is shown in Maven output if debug logs are enabled.
177      */
178     @Override
179     public String toString() {
180         return file.toString();
181     }
182 }