Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2013 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 |
* Portions Copyrighted 2013 Sun Microsystems, Inc. |
41 |
*/ |
42 |
package org.netbeans.api.java.source.support; |
43 |
|
44 |
import java.io.File; |
45 |
import java.io.IOException; |
46 |
import java.io.InputStream; |
47 |
import java.net.URI; |
48 |
import java.net.URL; |
49 |
import java.util.ArrayDeque; |
50 |
import java.util.Collection; |
51 |
import java.util.Collections; |
52 |
import java.util.EnumSet; |
53 |
import java.util.Enumeration; |
54 |
import java.util.LinkedHashMap; |
55 |
import java.util.Map; |
56 |
import java.util.Queue; |
57 |
import java.util.Set; |
58 |
import java.util.concurrent.ConcurrentHashMap; |
59 |
import java.util.concurrent.ConcurrentMap; |
60 |
import java.util.concurrent.Executor; |
61 |
import java.util.jar.Attributes; |
62 |
import java.util.jar.Manifest; |
63 |
import java.util.logging.Level; |
64 |
import java.util.logging.Logger; |
65 |
import javax.lang.model.element.ElementKind; |
66 |
import javax.lang.model.element.TypeElement; |
67 |
import javax.tools.JavaFileObject; |
68 |
import org.netbeans.api.annotations.common.CheckForNull; |
69 |
import org.netbeans.api.annotations.common.NonNull; |
70 |
import org.netbeans.api.annotations.common.NullAllowed; |
71 |
import org.netbeans.api.java.queries.SourceLevelQuery.Profile; |
72 |
import org.netbeans.api.java.source.ElementHandle; |
73 |
import org.netbeans.modules.classfile.Annotation; |
74 |
import org.netbeans.modules.classfile.AnnotationComponent; |
75 |
import org.netbeans.modules.classfile.CPEntry; |
76 |
import org.netbeans.modules.classfile.ClassFile; |
77 |
import org.netbeans.modules.classfile.ClassName; |
78 |
import org.netbeans.modules.classfile.ElementValue; |
79 |
import org.netbeans.modules.classfile.PrimitiveElementValue; |
80 |
import org.netbeans.modules.java.source.ElementHandleAccessor; |
81 |
import org.netbeans.modules.java.source.indexing.JavaIndex; |
82 |
import org.netbeans.modules.java.source.parsing.Archive; |
83 |
import org.netbeans.modules.java.source.parsing.CachingArchiveProvider; |
84 |
import org.netbeans.modules.java.source.parsing.FileObjects; |
85 |
import org.openide.filesystems.FileObject; |
86 |
import org.openide.filesystems.FileUtil; |
87 |
import org.openide.filesystems.URLMapper; |
88 |
import org.openide.util.Exceptions; |
89 |
import org.openide.util.Parameters; |
90 |
import org.openide.util.RequestProcessor; |
91 |
import org.openide.util.Union2; |
92 |
import org.openide.util.Utilities; |
93 |
|
94 |
/** |
95 |
* Utility methods for JDK 8 Profiles. |
96 |
* @author Tomas Zezula |
97 |
* @since 0.119 |
98 |
*/ |
99 |
public class ProfileSupport { |
100 |
|
101 |
private static final String RES_MANIFEST = "META-INF/MANIFEST.MF"; //NOI18N |
102 |
private static final String ATTR_PROFILE = "Profile"; //NOI18N |
103 |
private static final String ANNOTATION_PROFILE = "jdk/Profile+Annotation"; //NOI18N |
104 |
private static final String ANNOTATION_VALUE = "value"; //NOI18N |
105 |
private static final Logger LOG = Logger.getLogger(ProfileSupport.class.getName()); |
106 |
private static final RequestProcessor RP = new RequestProcessor(ProfileSupport.class); |
107 |
|
108 |
private ProfileSupport() {} |
109 |
|
110 |
/** |
111 |
* Kind of profile validation. |
112 |
*/ |
113 |
public enum Validation { |
114 |
/** |
115 |
* Request to validate sources on source path. |
116 |
*/ |
117 |
SOURCES, |
118 |
|
119 |
/** |
120 |
* Request to validate binaries on compile class path by Profile attribute in Manifest. |
121 |
* The manifest based validation does not analyze class files and is significantly faster |
122 |
* compared to {@link Validation#BINARIES_BY_CLASS_FILES} but does not work for jar |
123 |
* files with no Profile attributes. |
124 |
*/ |
125 |
BINARIES_BY_MANIFEST, |
126 |
|
127 |
/** |
128 |
* Request to validate binaries on compile class path by references in class files. |
129 |
*/ |
130 |
BINARIES_BY_CLASS_FILES |
131 |
} |
132 |
|
133 |
/** |
134 |
* Violation of profile. |
135 |
* The violation can be caused either by jar file on classpath requiring |
136 |
* a higher profile in manifest or by a source or class file referring to |
137 |
* a type from higher profile. |
138 |
*/ |
139 |
public static class Violation { |
140 |
|
141 |
private final URL root; |
142 |
private final Profile profile; |
143 |
private final URL file; |
144 |
private final ElementHandle<TypeElement> type; |
145 |
|
146 |
private Violation( |
147 |
@NonNull final URL root, |
148 |
@NullAllowed final Profile profile, |
149 |
@NullAllowed final URL file, |
150 |
@NullAllowed final ElementHandle<TypeElement> type) { |
151 |
Parameters.notNull("root", root); //NOI18N |
152 |
this.root = root; |
153 |
this.profile = profile; |
154 |
this.file = file; |
155 |
this.type = type; |
156 |
} |
157 |
|
158 |
/** |
159 |
* Returns the root which violates the tested profile. |
160 |
* @return the root {@link URL} |
161 |
*/ |
162 |
@NonNull |
163 |
public URL getRoot() { |
164 |
return root; |
165 |
} |
166 |
|
167 |
/** |
168 |
* Returns the {@link Profile} required by a root or file. |
169 |
* @return the {@link Profile} required by a root in the manifest |
170 |
* or by class (source) file under the root. May return a null if the |
171 |
* manifest contains invalid profile attribute. |
172 |
*/ |
173 |
@CheckForNull |
174 |
public Profile getRequiredProfile() { |
175 |
return profile; |
176 |
} |
177 |
|
178 |
/** |
179 |
* Returns the file which violates the tested profile. |
180 |
* @return the file referring to a type from higher profile. May return |
181 |
* null if the whole archive requires a higher profile. |
182 |
*/ |
183 |
@CheckForNull |
184 |
public URL getFile() { |
185 |
return file; |
186 |
} |
187 |
|
188 |
/** |
189 |
* Returns the type from higher {@link Profile} causing the violation. |
190 |
* @return the type causing the violation or null if the whole archive |
191 |
* requires a higher profile. |
192 |
*/ |
193 |
@CheckForNull |
194 |
public ElementHandle<TypeElement> getUsedType() { |
195 |
return type; |
196 |
} |
197 |
} |
198 |
|
199 |
/** |
200 |
* Asynchronous callback for collecting the profile {@link Violation}s. |
201 |
* For each classpath or source root a new {@link ViolationCollector} is |
202 |
* created by the {@link ViolationCollectorFactory#create}. When the validation |
203 |
* of the root is done the {@link ViolationCollector#finished} is called. |
204 |
* Threading: Validations of individual roots may run in parallel depending |
205 |
* on the {@link Executor} throughput. |
206 |
*/ |
207 |
public interface ViolationCollector { |
208 |
/** |
209 |
* Called to report a {@link Profile} violation. |
210 |
* @param violation the {@link Violation} to be reported. |
211 |
*/ |
212 |
void reportProfileViolation(@NonNull Violation violation); |
213 |
/** |
214 |
* Called when the validation of whole a root has finished. |
215 |
*/ |
216 |
void finished(); |
217 |
} |
218 |
|
219 |
/** |
220 |
* Factory for {@link ViolationCollector}. |
221 |
* For each root a new {@link ViolationCollector} is created by the factory. |
222 |
* Threading: Validations of individual roots may run in parallel depending |
223 |
* on the {@link Executor} throughput. |
224 |
*/ |
225 |
public interface ViolationCollectorFactory { |
226 |
/** |
227 |
* Creates a new {@lni ViolationCollector} for given root. |
228 |
* @param root the root to be validated |
229 |
* @return a new {@link ViolationCollector} |
230 |
*/ |
231 |
@NonNull |
232 |
ViolationCollector create(@NonNull URL root); |
233 |
|
234 |
/** |
235 |
* Signals that the validation should be canceled. |
236 |
* @return if true the validation is canceled. |
237 |
*/ |
238 |
boolean isCancelled(); |
239 |
} |
240 |
|
241 |
/** |
242 |
* Asynchronously finds the {@link Profile} violations in given source and classpath roots. |
243 |
* @param profileToCheck the {@link Profile} to be verified |
244 |
* @param bootClassPath the boot classpath of JDK 8 platform to get the profile info from |
245 |
* @param compileClassPath the compile classpath to be validated |
246 |
* @param sourcePath the source path to be validated |
247 |
* @param check types of validation |
248 |
* @param collectorFactory the {@link Violation}s collector |
249 |
* @throws IllegalArgumentException if the bootClassPath is not a valid JDK 8 boot classpath |
250 |
*/ |
251 |
public static void findProfileViolations( |
252 |
@NonNull final Profile profileToCheck, |
253 |
@NonNull final Iterable<URL> bootClassPath, |
254 |
@NonNull final Iterable<URL> compileClassPath, |
255 |
@NonNull final Iterable<URL> sourcePath, |
256 |
@NonNull final Set<Validation> check, |
257 |
@NonNull final ViolationCollectorFactory collectorFactory) { |
258 |
findProfileViolations( |
259 |
profileToCheck, |
260 |
bootClassPath, |
261 |
compileClassPath, |
262 |
sourcePath, |
263 |
check, |
264 |
collectorFactory, |
265 |
RP); |
266 |
} |
267 |
|
268 |
/** |
269 |
* Synchronously finds the {@link Profile} violations in given source and classpath roots. |
270 |
* @param profileToCheck the {@link Profile} to be verified |
271 |
* @param bootClassPath the boot classpath of JDK 8 platform to get the profile info from |
272 |
* @param compileClassPath the compile classpath to be validated |
273 |
* @param sourcePath the source path to be validated |
274 |
* @param check types of validation |
275 |
* @return the {@link Collection} of found {@link Violation}s |
276 |
* @throws IllegalArgumentException if the bootClassPath is not a valid JDK 8 boot classpath |
277 |
*/ |
278 |
@NonNull |
279 |
public static Collection<Violation> findProfileViolations( |
280 |
@NonNull final Profile profileToCheck, |
281 |
@NonNull final Iterable<URL> bootClassPath, |
282 |
@NonNull final Iterable<URL> compileClassPath, |
283 |
@NonNull final Iterable<URL> sourcePath, |
284 |
@NonNull final Set<Validation> check) { |
285 |
final DefaultProfileViolationCollector collector = |
286 |
new DefaultProfileViolationCollector(); |
287 |
findProfileViolations( |
288 |
profileToCheck, |
289 |
bootClassPath, |
290 |
compileClassPath, |
291 |
sourcePath, |
292 |
check, |
293 |
collector, |
294 |
new CurrentThreadExecutor()); |
295 |
return collector.getViolations(); |
296 |
} |
297 |
|
298 |
/** |
299 |
* Asynchronously finds the {@link Profile} violations in given source and classpath roots. |
300 |
* @param profileToCheck the {@link Profile} to be verified |
301 |
* @param bootClassPath the boot classpath of JDK 8 platform to get the profile info from |
302 |
* @param compileClassPath the compile classpath to be validated |
303 |
* @param sourcePath the source path to be validated |
304 |
* @param check types of validation |
305 |
* @param collectorFactory the {@link Violation}s collector |
306 |
* @param executor to use for the asynchronous operation, may have higher throughput |
307 |
* @throws IllegalArgumentException if the bootClassPath is not a valid JDK 8 boot classpath |
308 |
*/ |
309 |
public static void findProfileViolations( |
310 |
@NonNull final Profile profileToCheck, |
311 |
@NonNull final Iterable<URL> bootClassPath, |
312 |
@NonNull final Iterable<URL> compileClassPath, |
313 |
@NonNull final Iterable<URL> sourcePath, |
314 |
@NonNull final Set<Validation> check, |
315 |
@NonNull final ViolationCollectorFactory collectorFactory, |
316 |
@NonNull final Executor executor) { |
317 |
Parameters.notNull("profileToCheck", profileToCheck); //NOI18N |
318 |
Parameters.notNull("compileClassPath", compileClassPath); //NOI18N |
319 |
Parameters.notNull("sourcePath", sourcePath); //NOI18N |
320 |
Parameters.notNull("check", check); //NOI18N |
321 |
Parameters.notNull("collectorFactory", collectorFactory); //NOI18N |
322 |
Parameters.notNull("executor", executor); //NOI18N |
323 |
final Context ctx = new Context(profileToCheck, bootClassPath, collectorFactory, check); |
324 |
if (check.contains(Validation.BINARIES_BY_MANIFEST) || |
325 |
check.contains(Validation.BINARIES_BY_CLASS_FILES)) { |
326 |
for (final URL compileRoot : compileClassPath) { |
327 |
executor.execute(Validator.forBinary(compileRoot, ctx)); |
328 |
} |
329 |
} |
330 |
if (check.contains(Validation.SOURCES)) { |
331 |
for (final URL sourceRoot : sourcePath) { |
332 |
executor.execute(Validator.forSource(sourceRoot, ctx)); |
333 |
} |
334 |
} |
335 |
} |
336 |
|
337 |
private static final class Context { |
338 |
|
339 |
private final ArchiveCache archiveCache; |
340 |
private final TypeCache typeCache; |
341 |
private final Profile profileToCheck; |
342 |
private final ViolationCollectorFactory factory; |
343 |
private final Set<Validation> validations; |
344 |
|
345 |
Context( |
346 |
@NonNull final Profile profileToCheck, |
347 |
@NonNull final Iterable<? extends URL> bootClassPath, |
348 |
@NonNull final ViolationCollectorFactory factory, |
349 |
@NonNull final Set<Validation> validations) { |
350 |
assert profileToCheck != null; |
351 |
assert bootClassPath != null; |
352 |
assert factory != null; |
353 |
assert validations != null; |
354 |
this.archiveCache = ArchiveCache.getInstance(); |
355 |
this.typeCache = TypeCache.newInstance(bootClassPath); |
356 |
this.profileToCheck = profileToCheck; |
357 |
this.factory = factory; |
358 |
this.validations = EnumSet.copyOf(validations); |
359 |
} |
360 |
|
361 |
@NonNull |
362 |
ArchiveCache getArchiveCache() { |
363 |
return archiveCache; |
364 |
} |
365 |
|
366 |
@NonNull |
367 |
TypeCache getTypeCache() { |
368 |
return typeCache; |
369 |
} |
370 |
|
371 |
@NonNull |
372 |
Profile getRequredProfile() { |
373 |
return profileToCheck; |
374 |
} |
375 |
|
376 |
@NonNull |
377 |
ViolationCollector newCollector(@NonNull final URL root) { |
378 |
return factory.create(root); |
379 |
} |
380 |
|
381 |
boolean shouldValidate(@NonNull final Validation validation) { |
382 |
return validations.contains(validation); |
383 |
} |
384 |
|
385 |
boolean isCancelled() { |
386 |
return factory.isCancelled(); |
387 |
} |
388 |
|
389 |
} |
390 |
|
391 |
private static abstract class Validator implements Runnable { |
392 |
|
393 |
protected final Context context; |
394 |
protected final URL root; |
395 |
|
396 |
|
397 |
Validator( |
398 |
@NonNull final URL root, |
399 |
@NonNull final Context context) { |
400 |
assert root != null; |
401 |
assert context != null; |
402 |
this.root = root; |
403 |
this.context = context; |
404 |
} |
405 |
|
406 |
@Override |
407 |
public final void run() { |
408 |
final ViolationCollector collector = context.newCollector(root); |
409 |
assert collector != null; |
410 |
try { |
411 |
validate(collector); |
412 |
} finally { |
413 |
collector.finished(); |
414 |
} |
415 |
} |
416 |
|
417 |
protected final void validateBinaryRoot( |
418 |
@NonNull final URL root, |
419 |
@NonNull final ViolationCollector collector) { |
420 |
final FileObject rootFo = URLMapper.findFileObject(root); |
421 |
if (rootFo == null) { |
422 |
return; |
423 |
} |
424 |
final Enumeration<? extends FileObject> children = rootFo.getChildren(true); |
425 |
while (children.hasMoreElements()) { |
426 |
final FileObject fo = children.nextElement(); |
427 |
if (isImportant(fo)) { |
428 |
validateBinaryFile(fo, collector); |
429 |
} |
430 |
} |
431 |
} |
432 |
|
433 |
@NonNull |
434 |
protected URL map(@NonNull final FileObject fo) { |
435 |
return fo.toURL(); |
436 |
} |
437 |
|
438 |
protected abstract void validate(@NonNull ViolationCollector collector); |
439 |
|
440 |
private boolean isImportant(@NonNull final FileObject file) { |
441 |
return file.isData() && |
442 |
(FileObjects.CLASS.equals(file.getExt()) || FileObjects.SIG.equals(file.getExt())); |
443 |
} |
444 |
|
445 |
private void validateBinaryFile( |
446 |
@NonNull final FileObject fo, |
447 |
@NonNull final ViolationCollector collector) { |
448 |
final Profile profileToCheck = context.getRequredProfile(); |
449 |
final TypeCache tc = context.getTypeCache(); |
450 |
try { |
451 |
try (InputStream in = fo.getInputStream()) { |
452 |
ClassFile cf = new ClassFile(in); |
453 |
for (ClassName className : cf.getAllClassNames()) { |
454 |
final Profile p = tc.profileForType(className); |
455 |
if (p != null && profileToCheck.compareTo(p) < 0) { |
456 |
collector.reportProfileViolation( |
457 |
new Violation( |
458 |
root, |
459 |
p, |
460 |
map(fo), |
461 |
ElementHandleAccessor.getInstance().create(ElementKind.CLASS, className.getInternalName().replace('/', '.')) //NOI18N |
462 |
)); |
463 |
} |
464 |
} |
465 |
} |
466 |
} catch (IOException ioe) { |
467 |
LOG.log( |
468 |
Level.INFO, |
469 |
"Cannot validate file: {0}", //NOI18N |
470 |
FileUtil.getFileDisplayName(fo)); |
471 |
} |
472 |
} |
473 |
|
474 |
static Validator forSource( |
475 |
@NonNull final URL root, |
476 |
@NonNull final Context context) { |
477 |
return new SourceValidator(root, context); |
478 |
} |
479 |
|
480 |
static Validator forBinary( |
481 |
@NonNull final URL root, |
482 |
@NonNull final Context context) { |
483 |
return new BinaryValidator(root, context); |
484 |
} |
485 |
|
486 |
private final static class BinaryValidator extends Validator { |
487 |
|
488 |
private BinaryValidator( |
489 |
@NonNull final URL root, |
490 |
@NonNull final Context context) { |
491 |
super(root, context); |
492 |
} |
493 |
|
494 |
@Override |
495 |
protected void validate(@NonNull final ViolationCollector collector) { |
496 |
Profile current = null; |
497 |
if (context.shouldValidate(Validation.BINARIES_BY_MANIFEST)) { |
498 |
final Union2<Profile,String> res = findProfileInManifest(root); |
499 |
if (!res.hasFirst()) { |
500 |
//Invalid value of profile in manifest of dependent jar |
501 |
collector.reportProfileViolation(new Violation(root, null, null, null)); |
502 |
return; |
503 |
} |
504 |
if (res.first().compareTo(context.getRequredProfile()) > 0) { |
505 |
//Hiher profile in manifest of dependent jar |
506 |
collector.reportProfileViolation(new Violation(root, res.first(), null, null)); |
507 |
return; |
508 |
} |
509 |
current = res.first(); |
510 |
} |
511 |
if (context.shouldValidate(Validation.BINARIES_BY_CLASS_FILES)) { |
512 |
if (current == null || current == Profile.DEFAULT) { |
513 |
validateBinaryRoot(root, collector); |
514 |
} |
515 |
} |
516 |
} |
517 |
|
518 |
@NonNull |
519 |
private Union2<Profile,String> findProfileInManifest(@NonNull URL root) { |
520 |
final ArchiveCache ac = context.getArchiveCache(); |
521 |
Union2<Profile,String> res; |
522 |
final ArchiveCache.Key key = ac.createKey(root); |
523 |
if (key != null) { |
524 |
res = ac.getProfile(key); |
525 |
if (res != null) { |
526 |
return res; |
527 |
} |
528 |
} |
529 |
String profileName = null; |
530 |
final FileObject rootFo = URLMapper.findFileObject(root); |
531 |
if (rootFo != null) { |
532 |
final FileObject manifestFile = rootFo.getFileObject(RES_MANIFEST); |
533 |
if (manifestFile != null) { |
534 |
try { |
535 |
try (InputStream in = manifestFile.getInputStream()) { |
536 |
final Manifest manifest = new Manifest(in); |
537 |
final Attributes attrs = manifest.getMainAttributes(); |
538 |
profileName = attrs.getValue(ATTR_PROFILE); |
539 |
} |
540 |
} catch (IOException ioe) { |
541 |
LOG.log( |
542 |
Level.INFO, |
543 |
"Cannot read Profile attribute from: {0}", //NOI18N |
544 |
FileUtil.getFileDisplayName(manifestFile)); |
545 |
} |
546 |
} |
547 |
} |
548 |
final Profile profile = Profile.forName(profileName); |
549 |
res = profile != null ? |
550 |
Union2.<Profile,String>createFirst(profile) : |
551 |
Union2.<Profile,String>createSecond(profileName); |
552 |
if (key != null) { |
553 |
ac.putProfile(key, res); |
554 |
} |
555 |
return res; |
556 |
} |
557 |
|
558 |
} |
559 |
|
560 |
private final static class SourceValidator extends Validator { |
561 |
private SourceValidator( |
562 |
@NonNull final URL root, |
563 |
@NonNull final Context context) { |
564 |
super(root, context); |
565 |
} |
566 |
|
567 |
@Override |
568 |
protected void validate(@NonNull final ViolationCollector collector) { |
569 |
try { |
570 |
final File cacheRoot = JavaIndex.getClassFolder(root, true); |
571 |
if (cacheRoot != null) { |
572 |
validateBinaryRoot(Utilities.toURI(cacheRoot).toURL(), collector); |
573 |
} |
574 |
} catch (IOException e) { |
575 |
Exceptions.printStackTrace(e); |
576 |
} |
577 |
} |
578 |
} |
579 |
} |
580 |
|
581 |
|
582 |
private static class CurrentThreadExecutor implements Executor { |
583 |
@Override |
584 |
public void execute(Runnable command) { |
585 |
command.run(); |
586 |
} |
587 |
} |
588 |
|
589 |
private static class DefaultProfileViolationCollector implements ViolationCollectorFactory, ViolationCollector { |
590 |
|
591 |
private final Queue<Violation> violations = new ArrayDeque<>(); |
592 |
|
593 |
@Override |
594 |
public ViolationCollector create(@NonNull final URL root) { |
595 |
return this; |
596 |
} |
597 |
|
598 |
@Override |
599 |
public boolean isCancelled() { |
600 |
return false; |
601 |
} |
602 |
|
603 |
@Override |
604 |
public void reportProfileViolation(@NonNull final Violation violation) { |
605 |
violations.offer(violation); |
606 |
} |
607 |
|
608 |
@Override |
609 |
public void finished() { |
610 |
} |
611 |
|
612 |
Collection<Violation> getViolations() { |
613 |
return Collections.unmodifiableCollection(violations); |
614 |
} |
615 |
} |
616 |
|
617 |
//@ThreadSafe |
618 |
private static final class ArchiveCache { |
619 |
|
620 |
private static final int MAX_CACHE_SIZE = Integer.getInteger( |
621 |
"ProfileSupport.ArchiveCache.size", //NOI18N |
622 |
1<<10); |
623 |
|
624 |
//@GuardedBy("ArchiveCache.class") |
625 |
private static volatile ArchiveCache instance; |
626 |
|
627 |
//@GuardedBy("cache") |
628 |
private final Map<Key,Union2<Profile,String>> cache; |
629 |
|
630 |
private ArchiveCache() { |
631 |
this.cache = Collections.synchronizedMap(new LinkedHashMap<Key,Union2<Profile,String>>(16, 0.75f, true) { |
632 |
@Override |
633 |
protected boolean removeEldestEntry(Map.Entry<Key, Union2<Profile,String>> entry) { |
634 |
return size() > MAX_CACHE_SIZE; |
635 |
} |
636 |
}); |
637 |
} |
638 |
|
639 |
@NonNull |
640 |
static ArchiveCache getInstance() { |
641 |
ArchiveCache cache = instance; |
642 |
if (cache == null) { |
643 |
synchronized (ArchiveCache.class) { |
644 |
cache = instance; |
645 |
if (cache == null) { |
646 |
instance = cache = new ArchiveCache(); |
647 |
} |
648 |
} |
649 |
} |
650 |
return cache; |
651 |
} |
652 |
|
653 |
@CheckForNull |
654 |
Union2<Profile,String> getProfile(@NonNull final Key key) { |
655 |
final Union2<Profile,String> res = cache.get(key); |
656 |
if (LOG.isLoggable(Level.FINER)) { |
657 |
LOG.log( |
658 |
Level.FINER, |
659 |
"cache[{0}]->{1}", //NOI18N |
660 |
new Object[]{ |
661 |
key, |
662 |
res.hasFirst() ? res.first() : res.second() |
663 |
}); |
664 |
} |
665 |
return res; |
666 |
} |
667 |
|
668 |
void putProfile( |
669 |
@NonNull final Key key, |
670 |
@NonNull final Union2<Profile,String> profile) { |
671 |
if (LOG.isLoggable(Level.FINER)) { |
672 |
LOG.log( |
673 |
Level.FINER, |
674 |
"cache[{0}]<-{1}", //NOI18N |
675 |
new Object[]{ |
676 |
key, |
677 |
profile.hasFirst() ? profile.first() : profile.second() |
678 |
}); |
679 |
} |
680 |
cache.put(key,profile); |
681 |
} |
682 |
|
683 |
@CheckForNull |
684 |
Key createKey(@NonNull final URL rootURL) { |
685 |
final URL fileURL = FileUtil.getArchiveFile(rootURL); |
686 |
if (fileURL == null) { |
687 |
//Not an archive |
688 |
return null; |
689 |
} |
690 |
final FileObject fileFo = URLMapper.findFileObject(fileURL); |
691 |
if (fileFo == null) { |
692 |
return null; |
693 |
} |
694 |
return new Key( |
695 |
fileFo.toURI(), |
696 |
fileFo.lastModified().getTime(), |
697 |
fileFo.getSize()); |
698 |
} |
699 |
|
700 |
private static final class Key { |
701 |
|
702 |
private final URI root; |
703 |
private final long mtime; |
704 |
private final long size; |
705 |
|
706 |
Key( |
707 |
@NonNull final URI root, |
708 |
final long mtime, |
709 |
final long size) { |
710 |
this.root = root; |
711 |
this.mtime = mtime; |
712 |
this.size = size; |
713 |
} |
714 |
|
715 |
@Override |
716 |
public int hashCode() { |
717 |
int hash = 17; |
718 |
hash = 31 * hash + (this.root != null ? this.root.hashCode() : 0); |
719 |
hash = 31 * hash + (int) (this.mtime ^ (this.mtime >>> 32)); |
720 |
hash = 31 * hash + (int) (this.size ^ (this.size >>> 32)); |
721 |
return hash; |
722 |
} |
723 |
|
724 |
@Override |
725 |
public boolean equals(Object obj) { |
726 |
if (obj == this) { |
727 |
return true; |
728 |
} |
729 |
if (!(obj instanceof Key)) { |
730 |
return false; |
731 |
} |
732 |
final Key other = (Key) obj; |
733 |
return this.root.equals(other.root) && |
734 |
this.mtime == other.mtime && |
735 |
this.size == other.size; |
736 |
|
737 |
} |
738 |
|
739 |
@Override |
740 |
public String toString() { |
741 |
return String.format( |
742 |
"Key{root: %s, mtime: %d, size: %d}", //NOI18N |
743 |
root, |
744 |
mtime, |
745 |
size); |
746 |
} |
747 |
} |
748 |
} |
749 |
|
750 |
//@ThreadSafe |
751 |
private static final class TypeCache { |
752 |
|
753 |
private final Object UNKNOWN = new Object(); |
754 |
private final ConcurrentMap<String,Object> cache; |
755 |
private final Archive ctSym; |
756 |
|
757 |
private TypeCache(@NonNull final Archive ctSym) { |
758 |
assert ctSym != null; |
759 |
this.ctSym = ctSym; |
760 |
cache = new ConcurrentHashMap<>(); |
761 |
} |
762 |
|
763 |
@NonNull |
764 |
static TypeCache newInstance(Iterable<? extends URL> bootClassPath) { |
765 |
Archive ctSym = null; |
766 |
final CachingArchiveProvider ap = CachingArchiveProvider.getDefault(); |
767 |
for (URL root : bootClassPath) { |
768 |
if (ap.hasCtSym(root)) { |
769 |
ctSym = ap.getArchive(root, true); |
770 |
break; |
771 |
} |
772 |
} |
773 |
if (ctSym == null) { |
774 |
throw new IllegalArgumentException( |
775 |
String.format( |
776 |
"No profile info for boot classpath: %s", //NOI18N |
777 |
bootClassPath)); |
778 |
} |
779 |
return new TypeCache(ctSym); |
780 |
} |
781 |
|
782 |
@CheckForNull |
783 |
Profile profileForType(@NonNull final ClassName className) { |
784 |
final String binName = className.getInternalName(); |
785 |
Object res = cache.get(binName); |
786 |
if (res == null) { |
787 |
res = findProfile(binName); |
788 |
cache.put(binName, res); |
789 |
} |
790 |
return res == UNKNOWN ? null : (Profile) res; |
791 |
} |
792 |
|
793 |
@NonNull |
794 |
private Object findProfile(@NonNull final String binaryName) { |
795 |
Object res = UNKNOWN; |
796 |
final StringBuilder sb = new StringBuilder(binaryName); |
797 |
sb.append('.'); //NOI18N |
798 |
sb.append(FileObjects.CLASS); |
799 |
try { |
800 |
final JavaFileObject jfo = ctSym.getFile(sb.toString()); |
801 |
if (jfo != null) { |
802 |
try (InputStream in = jfo.openInputStream()) { |
803 |
final ClassFile cf = new ClassFile(in); |
804 |
final Annotation a = cf.getAnnotation(ClassName.getClassName(ANNOTATION_PROFILE)); |
805 |
if (a == null) { |
806 |
res = Profile.COMPACT1; |
807 |
} else { |
808 |
final AnnotationComponent ac = a.getComponent(ANNOTATION_VALUE); |
809 |
res = profileFromAnnotationComponent(ac); |
810 |
} |
811 |
} |
812 |
} |
813 |
} catch (IOException ioe) { |
814 |
Exceptions.printStackTrace(ioe); |
815 |
} |
816 |
return res; |
817 |
} |
818 |
|
819 |
@NonNull |
820 |
private static Profile profileFromAnnotationComponent(@NullAllowed final AnnotationComponent ac) { |
821 |
if (ac == null) { |
822 |
return Profile.COMPACT1; |
823 |
} |
824 |
try { |
825 |
final ElementValue ev = ac.getValue(); |
826 |
if (!(ev instanceof PrimitiveElementValue)) { |
827 |
return Profile.COMPACT1; |
828 |
} |
829 |
final CPEntry cpEntry = ((PrimitiveElementValue)ev).getValue(); |
830 |
if (cpEntry.getTag() != 3) { |
831 |
return Profile.COMPACT1; |
832 |
} |
833 |
final int ordinal = (Integer) cpEntry.getValue(); |
834 |
if (ordinal <= 0) { |
835 |
return Profile.COMPACT1; |
836 |
} |
837 |
final Profile[] values = Profile.values(); |
838 |
if (ordinal >= values.length) { |
839 |
return Profile.DEFAULT; |
840 |
} |
841 |
return values[ordinal-1]; |
842 |
} catch (NumberFormatException nfe) { |
843 |
return Profile.COMPACT1; |
844 |
} |
845 |
} |
846 |
} |
847 |
|
848 |
} |