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.shared.filtering;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.nio.file.Path;
26  import java.util.Collections;
27  import java.util.Comparator;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Optional;
33  import java.util.Set;
34  
35  import org.codehaus.plexus.util.DirectoryScanner;
36  import org.codehaus.plexus.util.Scanner;
37  import org.sonatype.plexus.build.incremental.BuildContext;
38  
39  /**
40   * {@link TestIncrementalBuildContext} mock for testing purpose. It allows to
41   * check build behavior based on {@link #isUptodate(File, File)} return value.
42   *
43   * Constructor parameters allow to indicate folder/files to declare files as
44   * modified / deleted.
45   *
46   * hasDelta, isUptodate, newScanner, newDeleteScanner methods output consistent
47   * values based upon changedFiles / deletedFiles inputs.
48   *
49   * getRefreshFiles method allows to check files modified by build.
50   */
51  public class TestIncrementalBuildContext implements BuildContext {
52  
53      private final Path basedir;
54      private final Set<Path> refresh = new HashSet<Path>();
55      private final Set<Path> changedFiles = new HashSet<Path>();
56      private final Set<Path> deletedFiles = new HashSet<Path>();
57      private final Map<String, Object> context = new HashMap<>();
58  
59      public TestIncrementalBuildContext(Path basedir, Set<Path> changedFiles) {
60          this(basedir, changedFiles, null);
61      }
62  
63      public TestIncrementalBuildContext(Path basedir, Set<Path> changedFiles, Set<Path> deletedFiles) {
64          checkPath(basedir);
65          this.basedir = basedir;
66          Optional.ofNullable(changedFiles).ifPresent(this.changedFiles::addAll);
67          Optional.ofNullable(deletedFiles).ifPresent(this.deletedFiles::addAll);
68          this.changedFiles.forEach(TestIncrementalBuildContext::checkPath);
69          this.deletedFiles.forEach(TestIncrementalBuildContext::checkPath);
70      }
71  
72      public static void checkPath(Path path) {
73          if (!path.isAbsolute()) {
74              throw new IllegalStateException(String.format("Absolute path are expected. Failing path %s" + path));
75          }
76      }
77  
78      public Set<Path> getRefreshFiles() {
79          return Collections.unmodifiableSet(refresh);
80      }
81  
82      /**
83       * Check that relpath or parent folders of relpath is not listed in modified /
84       * deleted files.
85       */
86      @Override
87      public boolean hasDelta(String relpath) {
88          Path resolved = basedir.resolve(relpath);
89          Path candidate = resolved;
90          boolean changed = false;
91          while (candidate != null) {
92              changed = changedFiles.contains(candidate) || deletedFiles.contains(candidate);
93              if (changed || candidate.equals(basedir)) {
94                  break;
95              }
96              candidate = candidate.getParent();
97          }
98          return changed;
99      }
100 
101     @SuppressWarnings("unchecked")
102     @Override
103     public boolean hasDelta(@SuppressWarnings("rawtypes") List relpaths) {
104         return ((List<String>) relpaths).stream().anyMatch(this::hasDelta);
105     }
106 
107     @Override
108     public boolean hasDelta(File file) {
109         return hasDelta(file.getAbsolutePath());
110     }
111 
112     @Override
113     public boolean isIncremental() {
114         return true;
115     }
116 
117     @Override
118     public Scanner newDeleteScanner(File basedir) {
119         return new TestScanner(basedir, deletedFiles);
120     }
121 
122     @Override
123     public OutputStream newFileOutputStream(File file) throws IOException {
124         refresh(file);
125         return new FileOutputStream(file);
126     }
127 
128     @Override
129     public Scanner newScanner(final File basedir) {
130         return new TestScanner(basedir, changedFiles);
131     }
132 
133     @Override
134     public Scanner newScanner(File basedir, boolean ignoreDelta) {
135         if (ignoreDelta) {
136             DirectoryScanner directoryScanner = new DirectoryScanner();
137             directoryScanner.setBasedir(basedir);
138             return directoryScanner;
139         }
140 
141         return newScanner(basedir);
142     }
143 
144     @Override
145     public void refresh(File file) {
146         refresh.add(file.toPath());
147     }
148 
149     @Override
150     public Object getValue(String key) {
151         return context.get(key);
152     }
153 
154     @Override
155     public void setValue(String key, Object value) {
156         context.put(key, value);
157     }
158 
159     @Override
160     public void addError(File file, int line, int column, String message, Throwable cause) {}
161 
162     @Override
163     public void addWarning(File file, int line, int column, String message, Throwable cause) {}
164 
165     @Override
166     public void addMessage(File file, int line, int column, String message, int severity, Throwable cause) {}
167 
168     @Override
169     public void removeMessages(File file) {}
170 
171     @Override
172     public boolean isUptodate(File target, File source) {
173         return target != null
174                 && target.exists()
175                 && !hasDelta(target)
176                 && source != null
177                 && source.exists()
178                 && !hasDelta(source)
179                 && target.lastModified() > source.lastModified();
180     }
181 
182     private static final class TestScanner implements Scanner {
183         private final Path basedir;
184         private final Set<Path> files = new HashSet<>();
185 
186         private TestScanner(File basedir, Set<Path> files) {
187             this.basedir = basedir.toPath().toAbsolutePath();
188             Optional.ofNullable(files).ifPresent(this.files::addAll);
189         }
190 
191         @Override
192         public void addDefaultExcludes() {}
193 
194         @Override
195         public String[] getIncludedDirectories() {
196             return new String[0];
197         }
198 
199         @Override
200         public String[] getIncludedFiles() {
201             return files.stream()
202                     .filter(p -> p.startsWith(basedir))
203                     .map(p -> basedir.relativize(p))
204                     .map(Path::toString)
205                     .toArray(i -> new String[i]);
206         }
207 
208         @Override
209         public void scan() {}
210 
211         @Override
212         public void setExcludes(String[] excludes) {}
213 
214         @Override
215         public void setIncludes(String[] includes) {}
216 
217         @Override
218         public File getBasedir() {
219             return basedir.toFile();
220         }
221 
222         @Override
223         public void setFilenameComparator(Comparator<String> filenameComparator) {}
224     }
225 }