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.impl;
20  
21  import java.nio.file.Files;
22  import java.nio.file.Path;
23  import java.nio.file.Paths;
24  import java.util.Map;
25  import java.util.Optional;
26  import java.util.function.Predicate;
27  import java.util.stream.Collectors;
28  
29  import org.apache.maven.api.JavaToolchain;
30  import org.apache.maven.api.Toolchain;
31  import org.apache.maven.api.Version;
32  import org.apache.maven.api.VersionConstraint;
33  import org.apache.maven.api.annotations.Nonnull;
34  import org.apache.maven.api.di.Inject;
35  import org.apache.maven.api.di.Named;
36  import org.apache.maven.api.di.Singleton;
37  import org.apache.maven.api.services.ToolchainFactory;
38  import org.apache.maven.api.services.ToolchainFactoryException;
39  import org.apache.maven.api.services.VersionParser;
40  import org.apache.maven.api.services.VersionParserException;
41  import org.apache.maven.api.toolchain.ToolchainModel;
42  import org.apache.maven.api.xml.XmlNode;
43  import org.apache.maven.impl.util.Os;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  @Named("jdk")
48  @Singleton
49  public class DefaultJavaToolchainFactory implements ToolchainFactory {
50  
51      public static final String KEY_JAVAHOME = "jdkHome"; // NOI18N
52  
53      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultJavaToolchainFactory.class);
54  
55      final VersionParser versionParser;
56  
57      @Inject
58      public DefaultJavaToolchainFactory(VersionParser versionParser) {
59          this.versionParser = versionParser;
60      }
61  
62      @Nonnull
63      @Override
64      public JavaToolchain createToolchain(@Nonnull ToolchainModel model) {
65          // populate the provides section
66          Map<String, Predicate<String>> matchers = model.getProvides().entrySet().stream()
67                  .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, entry -> {
68                      String key = entry.getKey();
69                      String value = entry.getValue();
70                      if (value == null) {
71                          throw new ToolchainFactoryException(
72                                  "Provides token '" + key + "' doesn't have any value configured.");
73                      }
74                      return "version".equals(key) ? new VersionMatcher(versionParser, value) : new ExactMatcher(value);
75                  }));
76  
77          // compute and normalize the java home
78          XmlNode dom = model.getConfiguration();
79          XmlNode javahome = dom != null ? dom.child(KEY_JAVAHOME) : null;
80          if (javahome == null || javahome.value() == null) {
81              throw new ToolchainFactoryException(
82                      "Java toolchain without the " + KEY_JAVAHOME + " configuration element.");
83          }
84          Path normal = Paths.get(javahome.value()).normalize();
85          if (!Files.exists(normal)) {
86              throw new ToolchainFactoryException("Non-existing JDK home configuration at " + normal.toAbsolutePath());
87          }
88          String javaHome = normal.toString();
89  
90          Version javaVersion = model.getProvides().entrySet().stream()
91                  .filter(entry -> "version".equals(entry.getKey()))
92                  .map(Map.Entry::getValue)
93                  .map(versionParser::parseVersion)
94                  .findAny()
95                  .orElse(null);
96  
97          return new DefaultJavaToolchain(model, javaHome, javaVersion, matchers);
98      }
99  
100     @Nonnull
101     @Override
102     public Optional<Toolchain> createDefaultToolchain() {
103         return Optional.empty();
104     }
105 
106     static class DefaultJavaToolchain implements JavaToolchain {
107 
108         final ToolchainModel model;
109         final String javaHome;
110         final Map<String, Predicate<String>> matchers;
111 
112         private Version javaVersion;
113 
114         DefaultJavaToolchain(
115                 ToolchainModel model, String javaHome, Version javaVersion, Map<String, Predicate<String>> matchers) {
116             this.model = model;
117             this.javaHome = javaHome;
118             this.javaVersion = javaVersion;
119             this.matchers = matchers;
120         }
121 
122         @Override
123         public String getJavaHome() {
124             return javaHome;
125         }
126 
127         @Override
128         public Version getJavaVersion() {
129             return javaVersion;
130         }
131 
132         @Override
133         public String getType() {
134             return "jdk";
135         }
136 
137         @Override
138         public ToolchainModel getModel() {
139             return model;
140         }
141 
142         @Override
143         public String findTool(String toolName) {
144             Path toRet = findTool(toolName, Paths.get(getJavaHome()).normalize());
145             if (toRet != null) {
146                 return toRet.toAbsolutePath().toString();
147             }
148             return null;
149         }
150 
151         private static Path findTool(String toolName, Path installDir) {
152             Path bin = installDir.resolve("bin"); // NOI18N
153             if (Files.isDirectory(bin)) {
154                 if (Os.IS_WINDOWS) {
155                     Path tool = bin.resolve(toolName + ".exe");
156                     if (Files.exists(tool)) {
157                         return tool;
158                     }
159                 }
160                 Path tool = bin.resolve(toolName);
161                 if (Files.exists(tool)) {
162                     return tool;
163                 }
164             }
165             return null;
166         }
167 
168         @Override
169         public boolean matchesRequirements(Map<String, String> requirements) {
170             for (Map.Entry<String, String> requirement : requirements.entrySet()) {
171                 String key = requirement.getKey();
172 
173                 Predicate<String> matcher = matchers.get(key);
174 
175                 if (matcher == null) {
176                     LOGGER.debug("Toolchain {} is missing required property: {}", this, key);
177                     return false;
178                 }
179                 if (!matcher.test(requirement.getValue())) {
180                     LOGGER.debug("Toolchain {} doesn't match required property: {}", this, key);
181                     return false;
182                 }
183             }
184             return true;
185         }
186 
187         @Override
188         public String toString() {
189             return "JDK[" + getJavaHome() + "]";
190         }
191     }
192 
193     static final class ExactMatcher implements Predicate<String> {
194 
195         final String provides;
196 
197         ExactMatcher(String provides) {
198             this.provides = provides;
199         }
200 
201         @Override
202         public boolean test(String requirement) {
203             return provides.equalsIgnoreCase(requirement);
204         }
205 
206         @Override
207         public String toString() {
208             return provides;
209         }
210     }
211 
212     static final class VersionMatcher implements Predicate<String> {
213 
214         final VersionParser versionParser;
215         final Version version;
216 
217         VersionMatcher(VersionParser versionParser, String version) {
218             this.versionParser = versionParser;
219             this.version = versionParser.parseVersion(version);
220         }
221 
222         @Override
223         public boolean test(String requirement) {
224             try {
225                 VersionConstraint constraint = versionParser.parseVersionConstraint(requirement);
226                 return constraint.contains(version);
227             } catch (VersionParserException ex) {
228                 return false;
229             }
230         }
231 
232         @Override
233         public String toString() {
234             return version.toString();
235         }
236     }
237 }