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(boolean)
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       * Returns the file resulting from the compilation of this source file. If the output file has been
93       * {@linkplain javax.tools.JavaFileManager#getFileForOutput obtained from the compiler}, that value
94       * if returned. Otherwise if {@code infer} is {@code true}, then the output file is inferred using
95       * {@linkplain #toOutputFile heuristic rules}.
96       *
97       * @param  infer whether to allow this method to infer a default path using heuristic rules
98       * @return path to the output file, or {@code null} if unspecified and {@code infer} is {@code false}
99       */
100     Path getOutputFile(boolean infer) {
101         if (!infer) {
102             /*
103              * TODO: add a `setOutputFile(Path)` method after we clarified how to get this information from the compiler.
104              *       It may be from javax.tools.JavaFileManager.getFileForOutput(...).
105              */
106             return null;
107         }
108         if (outputFile == null) {
109             outputFile = toOutputFile(
110                     directory.root,
111                     directory.outputDirectory,
112                     file,
113                     directory.fileKind.extension,
114                     directory.outputFileKind.extension);
115         }
116         return outputFile;
117     }
118 
119     /**
120      * Infers the path to the output file using heuristic rules.
121      * If the extension of the file is the one of {@linkplain SourceDirectory#fileKind source file kind}
122      * (usually {@code ".java"}), then it is replaced by the extension specified in {@code outputFileKind}.
123      * Otherwise the extension is left unmodified. Then, the path is made relative to the output directory.
124      *
125      * @param sourceDirectory root directory of the source file
126      * @param outputDirectory output directory of the compiled file
127      * @param file path to the source file
128      * @param extension expected extension of the source file, leading dot included
129      * @param outext extension of the output file, leading dot included
130      * @return path to the target file
131      */
132     static Path toOutputFile(Path sourceDirectory, Path outputDirectory, Path file, String extension, String outext) {
133         Path output = sourceDirectory.relativize(file);
134         String filename = file.getFileName().toString();
135         if (filename.endsWith(extension)) {
136             filename = filename.substring(0, filename.length() - extension.length());
137             filename = filename.concat(outext);
138             output = output.resolveSibling(filename);
139         }
140         return outputDirectory.resolve(output);
141     }
142 
143     /**
144      * Compares the given object with this source file for equality.
145      * This method compares only the file path. Metadata such as last modification time are ignored.
146      *
147      * @param obj the object to compare
148      * @return whether the two objects have the same path and attributes
149      */
150     @Override
151     public boolean equals(Object obj) {
152         if (obj instanceof SourceFile other) {
153             return file.equals(other.file) && directory.equals(other.directory);
154         }
155         return false;
156     }
157 
158     /**
159      * {@return a hash code value for this file}.
160      */
161     @Override
162     public int hashCode() {
163         return directory.hashCode() + 7 * file.hashCode();
164     }
165 
166     /**
167      * {@return a string representation of this source file for debugging purposes}.
168      * This string representation is shown in Maven output if debug logs are enabled.
169      */
170     @Override
171     public String toString() {
172         return file.toString();
173     }
174 }