Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* If you wish your version of this file to be governed by only the CDDL |
28 |
* or only the GPL Version 2, indicate your decision by adding |
29 |
* "[Contributor] elects to include this software in this distribution |
30 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
31 |
* single choice of license, a recipient has the option to distribute |
32 |
* your version of this file under either the CDDL, the GPL Version 2 or |
33 |
* to extend the choice of license to its licensees as provided above. |
34 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
35 |
* Version 2 license, then the option applies only if the new code is |
36 |
* made subject to such option by the copyright holder. |
37 |
* |
38 |
* Contributor(s): |
39 |
*/ |
40 |
package org.netbeans.modules.java.api.common.impl; |
41 |
|
42 |
import com.sun.source.tree.AssignmentTree; |
43 |
import com.sun.source.tree.MemberSelectTree; |
44 |
import com.sun.source.tree.Tree; |
45 |
import com.sun.source.tree.Tree.Kind; |
46 |
import com.sun.source.util.TreePath; |
47 |
import com.sun.source.util.TreePathScanner; |
48 |
import java.io.IOException; |
49 |
import java.util.ArrayList; |
50 |
import java.util.Arrays; |
51 |
import java.util.Collections; |
52 |
import java.util.HashSet; |
53 |
import java.util.List; |
54 |
import java.util.Set; |
55 |
import javax.lang.model.element.Element; |
56 |
import javax.lang.model.element.ElementKind; |
57 |
import javax.lang.model.element.PackageElement; |
58 |
import javax.lang.model.type.TypeKind; |
59 |
import javax.lang.model.type.TypeMirror; |
60 |
import org.netbeans.api.java.classpath.ClassPath; |
61 |
import org.netbeans.api.java.queries.SourceLevelQuery; |
62 |
import org.netbeans.api.java.source.ClasspathInfo; |
63 |
import org.netbeans.api.java.source.ClasspathInfo.PathKind; |
64 |
import org.netbeans.api.java.source.CompilationController; |
65 |
import org.netbeans.api.java.source.CompilationInfo; |
66 |
import org.netbeans.api.java.source.JavaSource; |
67 |
import org.netbeans.api.java.source.SourceUtils; |
68 |
import org.netbeans.api.java.source.Task; |
69 |
import org.netbeans.api.templates.CreateDescriptor; |
70 |
import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
71 |
import org.openide.filesystems.FileObject; |
72 |
import org.openide.modules.SpecificationVersion; |
73 |
import org.openide.util.lookup.ServiceProvider; |
74 |
import org.netbeans.api.templates.CreateFromTemplateDecorator; |
75 |
|
76 |
/** |
77 |
* Automatically declares dependencies on modules used in template. |
78 |
* Scans the created file; if some unresolved identifier appears, which corresponds to a |
79 |
* class in platform or libraries, the decorator will add {@code requires} directive to the |
80 |
* appropriate {@code module-info.java}. Activates only on projects which have source level >= 9 |
81 |
* and contain module declaration. |
82 |
* |
83 |
* @author sdedic |
84 |
*/ |
85 |
@ServiceProvider(service = CreateFromTemplateDecorator.class) |
86 |
public final class TemplateModuleDeclarator implements CreateFromTemplateDecorator { |
87 |
private static final String NAME_MODULE_INFO = "module-info.java"; // NOI18N |
88 |
private static final String ATTRIBUTE_REQUIRED_MODULES = "requiredModules"; // NOI18N |
89 |
private static final SpecificationVersion SOURCE_LEVEL_9 = new SpecificationVersion("9"); // NOI18N |
90 |
private static final String MIME_JAVA = "text/x-java"; // NOI18N |
91 |
private static final String CLASS_EXTENSION = ".class"; // NOI18N |
92 |
|
93 |
@Override |
94 |
public boolean isBeforeCreation() { |
95 |
return false; |
96 |
} |
97 |
|
98 |
@Override |
99 |
public boolean isAfterCreation() { |
100 |
return true; |
101 |
} |
102 |
|
103 |
@Override |
104 |
public boolean accept(CreateDescriptor desc) { |
105 |
if (desc.getValue(ATTRIBUTE_REQUIRED_MODULES) != null) { |
106 |
return true; |
107 |
} |
108 |
FileObject t = desc.getTarget(); |
109 |
if (!MIME_JAVA.equals(desc.getTemplate().getMIMEType())) { |
110 |
return false; |
111 |
} |
112 |
String s = SourceLevelQuery.getSourceLevel(t); |
113 |
if (s == null || SOURCE_LEVEL_9.compareTo(new SpecificationVersion(s)) > 0) { |
114 |
return false; |
115 |
} |
116 |
// check module-info |
117 |
ClassPath srcPath = ClassPath.getClassPath(t, ClassPath.SOURCE); |
118 |
if (srcPath == null) { |
119 |
return false; |
120 |
} |
121 |
if (srcPath.findResource(NAME_MODULE_INFO) == null) { // NOI18N |
122 |
return false; |
123 |
} |
124 |
return true; |
125 |
} |
126 |
|
127 |
@Override |
128 |
public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException { |
129 |
List<FileObject> jsources = new ArrayList<>(createdFiles.size()); |
130 |
for (FileObject f : createdFiles) { |
131 |
if (MIME_JAVA.equals(f.getMIMEType())) { |
132 |
jsources.add(f); |
133 |
} |
134 |
} |
135 |
if (jsources.isEmpty()) { |
136 |
return null; |
137 |
} |
138 |
FileObject t = desc.getTarget(); |
139 |
// check module-info |
140 |
ClassPath srcPath = ClassPath.getClassPath(t, ClassPath.SOURCE); |
141 |
if (srcPath == null) { |
142 |
return null; |
143 |
} |
144 |
FileObject modinfo = srcPath.findResource(NAME_MODULE_INFO); // NOI18N |
145 |
if (modinfo == null) { |
146 |
return null; |
147 |
} |
148 |
|
149 |
Object a = desc.getTemplate().getAttribute(ATTRIBUTE_REQUIRED_MODULES); |
150 |
Set<String> modules; |
151 |
|
152 |
if (a instanceof String) { |
153 |
String s = a.toString(); |
154 |
if (!s.isEmpty()) { |
155 |
modules = new HashSet<>(Arrays.asList(s.split(","))); // NOI18N |
156 |
} else { |
157 |
modules = Collections.emptySet(); |
158 |
} |
159 |
} else { |
160 |
modules = inferModuleNames(t, jsources); |
161 |
} |
162 |
|
163 |
if (modules.isEmpty()) { |
164 |
return null; |
165 |
} |
166 |
DefaultProjectModulesModifier.addRequiredModules(modinfo, modules); |
167 |
return null; |
168 |
} |
169 |
|
170 |
private Set<String> inferModuleNames(FileObject t, List<FileObject> jsources) throws IOException { |
171 |
ClasspathInfo cpi = ClasspathInfo.create(t); |
172 |
JavaSource src = JavaSource.create(cpi, jsources); |
173 |
Set<String> unresolved = new HashSet<>(); |
174 |
src.runUserActionTask(new Task<CompilationController>() { |
175 |
@Override |
176 |
public void run(CompilationController parameter) throws Exception { |
177 |
parameter.toPhase(JavaSource.Phase.RESOLVED); |
178 |
Scanner s = new Scanner(parameter, unresolved); |
179 |
s.scan(parameter.getCompilationUnit(), null); |
180 |
} |
181 |
}, true); |
182 |
ClassPath searchPath = ClassPathSupport.createProxyClassPath( |
183 |
cpi.getClassPath(PathKind.MODULE_BOOT), |
184 |
cpi.getClassPath(PathKind.MODULE_COMPILE)); |
185 |
Set<String> moduleNames = new HashSet<>(); |
186 |
for (String fqn : unresolved) { |
187 |
findModuleNames(fqn, searchPath, moduleNames); |
188 |
} |
189 |
return moduleNames; |
190 |
} |
191 |
|
192 |
|
193 |
private void findModuleNames(String fqn, ClassPath searchPath, Set<String> moduleNames) { |
194 |
fqn = fqn.replace(".", "/"); |
195 |
String resourceName = fqn + CLASS_EXTENSION; |
196 |
FileObject classResource = searchPath.findResource(resourceName); |
197 |
if (classResource == null || classResource.isFolder()) { |
198 |
int last = fqn.length(); |
199 |
for (int i = fqn.lastIndexOf('.'); i >= 0; i = fqn.lastIndexOf('/', last)) { // NOI18N |
200 |
resourceName = fqn.substring(0, i); |
201 |
classResource = searchPath.findResource(resourceName + CLASS_EXTENSION); |
202 |
if (classResource != null && classResource.isData()) { |
203 |
resourceName += fqn.substring(i).replace("/", "$") + CLASS_EXTENSION; // NOI18N |
204 |
classResource = searchPath.findResource(resourceName); |
205 |
break; |
206 |
} |
207 |
classResource = searchPath.findResource(resourceName); |
208 |
if (classResource != null && classResource.isFolder()) { |
209 |
return; |
210 |
} |
211 |
} |
212 |
} |
213 |
if (classResource != null) { |
214 |
FileObject r = searchPath.findOwnerRoot(classResource); |
215 |
for (ClassPath.Entry e : searchPath.entries()) { |
216 |
if (e.getRoot().equals(r)) { |
217 |
String name = SourceUtils.getModuleName(e.getURL(), true); |
218 |
moduleNames.add(name); |
219 |
} |
220 |
} |
221 |
} |
222 |
} |
223 |
|
224 |
private static class Scanner extends TreePathScanner<Boolean, Boolean> { |
225 |
private final CompilationInfo info; |
226 |
private final Set<String> unresolved; |
227 |
|
228 |
public Scanner(CompilationInfo info, Set<String> s) { |
229 |
this.info = info; |
230 |
this.unresolved = s; |
231 |
} |
232 |
|
233 |
@Override |
234 |
public Boolean visitMemberSelect(MemberSelectTree node, Boolean p) { |
235 |
Boolean r = super.scan(node.getExpression(), p); |
236 |
if (r) { |
237 |
return r; |
238 |
} |
239 |
TreePath parentPath = getCurrentPath().getParentPath(); |
240 |
if (parentPath == null) { |
241 |
return null; |
242 |
} |
243 |
Tree t = node; |
244 |
Tree par = parentPath.getLeaf(); |
245 |
if (node.getIdentifier().contentEquals("*")) { // NOI18N |
246 |
// assume import, discard one level |
247 |
t = node.getExpression(); |
248 |
par = node; |
249 |
parentPath = new TreePath(parentPath, t); |
250 |
} |
251 |
if (par.getKind() == Kind.METHOD_INVOCATION) { |
252 |
return null; |
253 |
} |
254 |
final Element el = info.getTrees().getElement(getCurrentPath()); |
255 |
if (el == null || !(el.getKind().isClass() || el.getKind().isInterface() || el.getKind() == ElementKind.PACKAGE)) { |
256 |
return true; |
257 |
} |
258 |
TypeMirror type = el.asType(); |
259 |
if (type == null) { |
260 |
return null; |
261 |
} |
262 |
String fqn = null; |
263 |
|
264 |
if (type.getKind() == TypeKind.ERROR) { |
265 |
if (par.getKind() == Tree.Kind.ASSIGNMENT) { |
266 |
AssignmentTree at = (AssignmentTree) getCurrentPath().getParentPath().getLeaf(); |
267 |
|
268 |
if (at.getVariable() == node) { |
269 |
return null; |
270 |
} |
271 |
} |
272 |
} else if (type.getKind() == TypeKind.PACKAGE) { |
273 |
String s = ((PackageElement) el).getQualifiedName().toString(); |
274 |
if (info.getElements().getPackageElement(s) != null) { |
275 |
return null; |
276 |
} |
277 |
} |
278 |
fqn = node.toString(); |
279 |
if (fqn.endsWith(".<error>")) { // NOI18N |
280 |
fqn = fqn.substring(0, fqn.lastIndexOf(".")); // NOI18N |
281 |
} |
282 |
unresolved.add(fqn); |
283 |
return null; |
284 |
} |
285 |
} |
286 |
} |