--- a/java.project/nbproject/project.xml
+++ a/java.project/nbproject/project.xml
@@ -128,6 +128,14 @@
+ org.netbeans.modules.java.source
+
+
+
+ 0.118
+
+
+
org.netbeans.modules.project.ant
--- a/java.project/src/org/netbeans/modules/java/project/ProfileProblemsProviderImpl.java
+++ a/java.project/src/org/netbeans/modules/java/project/ProfileProblemsProviderImpl.java
@@ -45,28 +45,22 @@
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
-import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.JButton;
@@ -78,6 +72,7 @@
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.queries.SourceLevelQuery;
import org.netbeans.api.java.queries.SourceLevelQuery.Profile;
+import org.netbeans.api.java.source.support.ProfileSupport;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
@@ -89,7 +84,6 @@
import org.netbeans.spi.project.support.ant.ReferenceHelper;
import org.netbeans.spi.project.ui.ProjectProblemsProvider;
import org.netbeans.spi.project.ui.support.ProjectProblemsProviderSupport;
-import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
@@ -105,10 +99,8 @@
import org.netbeans.spi.java.classpath.ClassPathFactory;
import org.netbeans.spi.java.project.classpath.support.ProjectClassPathSupport;
import org.netbeans.spi.project.support.ant.PropertyUtils;
-import org.openide.filesystems.URLMapper;
import org.openide.util.ImageUtilities;
import org.openide.util.RequestProcessor;
-import org.openide.util.Union2;
/**
*
* @author Tomas Zezula
@@ -326,11 +318,17 @@
final String libName = rawEntry.substring(LIB_PREFIX.length(),rawEntry.lastIndexOf('.'));
final Library lib = refHelper.findLibrary(libName);
if (lib != null) {
- final Union2 minProfileOrName = findProfile(lib.getContent(VOL_CLASSPATH));
- final Profile minProfile = minProfileOrName.hasFirst() ? minProfileOrName.first() : null;
- if (isBroken(requiredProfile, minProfile)) {
- collector.add(new LibraryReference(classPathId, rawEntry, minProfile, lib));
- }
+ final Collection extends ProfileSupport.Violation> res =
+ ProfileSupport.findProfileViolations(
+ requiredProfile,
+ Collections.emptySet(),
+ lib.getContent(VOL_CLASSPATH),
+ Collections.emptySet(),
+ EnumSet.of(ProfileSupport.Validation.BINARIES_BY_MANIFEST));
+ if (!res.isEmpty()) {
+ final Profile maxProfile = findMaxProfile(res);
+ collector.add(new LibraryReference(classPathId, rawEntry, maxProfile, lib));
+ }
}
} else if (rawEntry.startsWith(PRJ_PREFIX)) {
final Object[] ref = refHelper.findArtifactAndLocation(rawEntry);
@@ -350,10 +348,16 @@
final File file = antProjectHelper.resolveFile(path);
final URL root = FileUtil.urlForArchiveOrDir(file);
if (root != null) {
- final Union2 minProfileOrName = findProfile(root);
- final Profile minProfile = minProfileOrName.hasFirst() ? minProfileOrName.first() : null;
- if (isBroken(requiredProfile, minProfile)) {
- collector.add(new FileReference(classPathId, rawEntry, minProfile, file));
+ final Collection extends ProfileSupport.Violation> res =
+ ProfileSupport.findProfileViolations(
+ requiredProfile,
+ Collections.emptySet(),
+ Collections.singleton(root),
+ Collections.emptySet(),
+ EnumSet.of(ProfileSupport.Validation.BINARIES_BY_MANIFEST));
+ if (!res.isEmpty()) {
+ final Profile maxProfile = findMaxProfile(res);
+ collector.add(new FileReference(classPathId, rawEntry, maxProfile, file));
}
}
}
@@ -373,10 +377,16 @@
final File file = antProjectHelper.resolveFile(propName);
final URL root = FileUtil.urlForArchiveOrDir(file);
if (root != null) {
- final Union2 minProfileOrName = findProfile(root);
- final Profile minProfile = minProfileOrName.hasFirst() ? minProfileOrName.first() : null;
- if (isBroken(requiredProfile, minProfile)) {
- collector.add(new FileReference(classPathId, rawEntry, minProfile, file));
+ final Collection extends ProfileSupport.Violation> res =
+ ProfileSupport.findProfileViolations(
+ requiredProfile,
+ Collections.emptySet(),
+ Collections.singleton(root),
+ Collections.emptySet(),
+ EnumSet.of(ProfileSupport.Validation.BINARIES_BY_MANIFEST));
+ if (!res.isEmpty()) {
+ final Profile maxProfile = findMaxProfile(res);
+ collector.add(new FileReference(classPathId, rawEntry, maxProfile, file));
}
}
}
@@ -405,60 +415,20 @@
return reference;
}
- @NonNull
- private static Union2 findProfile(@NonNull final Iterable extends URL> roots) {
- Profile current = Profile.DEFAULT;
- for (URL root : roots) {
- final Union2 rootProfile = findProfile(root);
- if (!rootProfile.hasFirst()) {
+ @CheckForNull
+ private static Profile findMaxProfile(@NonNull final Iterable extends ProfileSupport.Violation> violations) {
+ Profile current = null;
+ for (ProfileSupport.Violation violation : violations) {
+ Profile p = violation.getRequiredProfile();
+ if (p == null) {
//Broken profile - no need to continue
- return rootProfile;
+ return null;
}
- current = max(current, rootProfile.first());
+ current = max(current, p);
}
- return Union2.createFirst(current);
+ return current;
}
-
- @NonNull
- private static Union2 findProfile(@NonNull URL root) {
- Union2 res;
- final ArchiveCache.Key key = ArchiveCache.createKey(root);
- if (key != null) {
- res = ArchiveCache.getProfile(key);
- if (res != null) {
- return res;
- }
- }
- String profileName = null;
- final FileObject rootFo = URLMapper.findFileObject(root);
- if (rootFo != null) {
- final FileObject manifestFile = rootFo.getFileObject(RES_MANIFEST);
- if (manifestFile != null) {
- try {
- try (InputStream in = manifestFile.getInputStream()) {
- final Manifest manifest = new Manifest(in);
- final Attributes attrs = manifest.getMainAttributes();
- profileName = attrs.getValue(ATTR_PROFILE);
- }
- } catch (IOException ioe) {
- LOG.log(
- Level.INFO,
- "Cannot read Profile attribute from: {0}", //NOI18N
- FileUtil.getFileDisplayName(manifestFile));
- }
- }
- }
- final Profile profile = Profile.forName(profileName);
- res = profile != null ?
- Union2.createFirst(profile) :
- Union2.createSecond(profileName);
- if (key != null) {
- ArchiveCache.putProfile(key, res);
- }
- return res;
- }
-
-
+
@CheckForNull
static Profile max (@NullAllowed Profile a, @NullAllowed Profile b) {
if (b == null) {
@@ -745,118 +715,5 @@
res.run();
return res;
}
- }
-
-
- private static final class ArchiveCache {
-
- private static final int MAX_CACHE_SIZE = Integer.getInteger(
- "ProfileProblemsProviderImpl.ArchiveCache.size", //NOI18N
- 1<<10);
-
- private static final Map> cache = new LinkedHashMap>(16, 0.75f, true) {
- @Override
- protected boolean removeEldestEntry(Map.Entry> entry) {
- return size() > MAX_CACHE_SIZE;
- }
- };
-
- private ArchiveCache() {}
-
- @CheckForNull
- static Union2 getProfile(@NonNull final Key key) {
- final Union2 res = cache.get(key);
- if (LOG.isLoggable(Level.FINER)) {
- LOG.log(
- Level.FINER,
- "cache[{0}]->{1}", //NOI18N
- new Object[]{
- key,
- res.hasFirst() ? res.first() : res.second()
- });
- }
- return res;
- }
-
- static void putProfile(
- @NonNull final Key key,
- @NonNull final Union2 profile) {
- if (LOG.isLoggable(Level.FINER)) {
- LOG.log(
- Level.FINER,
- "cache[{0}]<-{1}", //NOI18N
- new Object[]{
- key,
- profile.hasFirst() ? profile.first() : profile.second()
- });
- }
- cache.put(key,profile);
- }
-
- @CheckForNull
- static Key createKey(@NonNull final URL rootURL) {
- final URL fileURL = FileUtil.getArchiveFile(rootURL);
- if (fileURL == null) {
- //Not an archive
- return null;
- }
- final FileObject fileFo = URLMapper.findFileObject(fileURL);
- if (fileFo == null) {
- return null;
- }
- return new Key(
- fileFo.toURI(),
- fileFo.lastModified().getTime(),
- fileFo.getSize());
- }
-
- private static final class Key {
-
- private final URI root;
- private final long mtime;
- private final long size;
-
- Key(
- @NonNull final URI root,
- final long mtime,
- final long size) {
- this.root = root;
- this.mtime = mtime;
- this.size = size;
- }
-
- @Override
- public int hashCode() {
- int hash = 17;
- hash = 31 * hash + (this.root != null ? this.root.hashCode() : 0);
- hash = 31 * hash + (int) (this.mtime ^ (this.mtime >>> 32));
- hash = 31 * hash + (int) (this.size ^ (this.size >>> 32));
- return hash;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) {
- return true;
- }
- if (!(obj instanceof Key)) {
- return false;
- }
- final Key other = (Key) obj;
- return this.root.equals(other.root) &&
- this.mtime == other.mtime &&
- this.size == other.size;
-
- }
-
- @Override
- public String toString() {
- return String.format(
- "Key{root: %s, mtime: %d, size: %d}", //NOI18N
- root,
- mtime,
- size);
- }
- }
- }
+ }
}
--- a/java.source/apichanges.xml
+++ a/java.source/apichanges.xml
@@ -108,6 +108,19 @@
+
+
+ Added support for JDK 8 Profiles.
+
+
+
+
+
+ Added ProfileSupport
class providing utility methods for JDK 8 profiles.
+
+
+
+
Added several methods to support Java 8 features.
--- a/java.source/nbproject/project.properties
+++ a/java.source/nbproject/project.properties
@@ -46,7 +46,7 @@
javadoc.title=Java Source
javadoc.arch=${basedir}/arch.xml
javadoc.apichanges=${basedir}/apichanges.xml
-spec.version.base=0.118.0
+spec.version.base=0.119.0
test.qa-functional.cp.extra=${refactoring.java.dir}/modules/ext/nb-javac-api.jar
test.unit.run.cp.extra=${o.n.core.dir}/core/core.jar:\
${o.n.core.dir}/lib/boot.jar:\
--- a/java.source/src/org/netbeans/api/java/source/support/ProfileSupport.java
+++ a/java.source/src/org/netbeans/api/java/source/support/ProfileSupport.java
@@ -0,0 +1,848 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.api.java.source.support;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaFileObject;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
+import org.netbeans.api.java.queries.SourceLevelQuery.Profile;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.modules.classfile.Annotation;
+import org.netbeans.modules.classfile.AnnotationComponent;
+import org.netbeans.modules.classfile.CPEntry;
+import org.netbeans.modules.classfile.ClassFile;
+import org.netbeans.modules.classfile.ClassName;
+import org.netbeans.modules.classfile.ElementValue;
+import org.netbeans.modules.classfile.PrimitiveElementValue;
+import org.netbeans.modules.java.source.ElementHandleAccessor;
+import org.netbeans.modules.java.source.indexing.JavaIndex;
+import org.netbeans.modules.java.source.parsing.Archive;
+import org.netbeans.modules.java.source.parsing.CachingArchiveProvider;
+import org.netbeans.modules.java.source.parsing.FileObjects;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Parameters;
+import org.openide.util.RequestProcessor;
+import org.openide.util.Union2;
+import org.openide.util.Utilities;
+
+/**
+ * Utility methods for JDK 8 Profiles.
+ * @author Tomas Zezula
+ * @since 0.119
+ */
+public class ProfileSupport {
+
+ private static final String RES_MANIFEST = "META-INF/MANIFEST.MF"; //NOI18N
+ private static final String ATTR_PROFILE = "Profile"; //NOI18N
+ private static final String ANNOTATION_PROFILE = "jdk/Profile+Annotation"; //NOI18N
+ private static final String ANNOTATION_VALUE = "value"; //NOI18N
+ private static final Logger LOG = Logger.getLogger(ProfileSupport.class.getName());
+ private static final RequestProcessor RP = new RequestProcessor(ProfileSupport.class);
+
+ private ProfileSupport() {}
+
+ /**
+ * Kind of profile validation.
+ */
+ public enum Validation {
+ /**
+ * Request to validate sources on source path.
+ */
+ SOURCES,
+
+ /**
+ * Request to validate binaries on compile class path by Profile attribute in Manifest.
+ * The manifest based validation does not analyze class files and is significantly faster
+ * compared to {@link Validation#BINARIES_BY_CLASS_FILES} but does not work for jar
+ * files with no Profile attributes.
+ */
+ BINARIES_BY_MANIFEST,
+
+ /**
+ * Request to validate binaries on compile class path by references in class files.
+ */
+ BINARIES_BY_CLASS_FILES
+ }
+
+ /**
+ * Violation of profile.
+ * The violation can be caused either by jar file on classpath requiring
+ * a higher profile in manifest or by a source or class file referring to
+ * a type from higher profile.
+ */
+ public static class Violation {
+
+ private final URL root;
+ private final Profile profile;
+ private final URL file;
+ private final ElementHandle type;
+
+ private Violation(
+ @NonNull final URL root,
+ @NullAllowed final Profile profile,
+ @NullAllowed final URL file,
+ @NullAllowed final ElementHandle type) {
+ Parameters.notNull("root", root); //NOI18N
+ this.root = root;
+ this.profile = profile;
+ this.file = file;
+ this.type = type;
+ }
+
+ /**
+ * Returns the root which violates the tested profile.
+ * @return the root {@link URL}
+ */
+ @NonNull
+ public URL getRoot() {
+ return root;
+ }
+
+ /**
+ * Returns the {@link Profile} required by a root or file.
+ * @return the {@link Profile} required by a root in the manifest
+ * or by class (source) file under the root. May return a null if the
+ * manifest contains invalid profile attribute.
+ */
+ @CheckForNull
+ public Profile getRequiredProfile() {
+ return profile;
+ }
+
+ /**
+ * Returns the file which violates the tested profile.
+ * @return the file referring to a type from higher profile. May return
+ * null if the whole archive requires a higher profile.
+ */
+ @CheckForNull
+ public URL getFile() {
+ return file;
+ }
+
+ /**
+ * Returns the type from higher {@link Profile} causing the violation.
+ * @return the type causing the violation or null if the whole archive
+ * requires a higher profile.
+ */
+ @CheckForNull
+ public ElementHandle getUsedType() {
+ return type;
+ }
+ }
+
+ /**
+ * Asynchronous callback for collecting the profile {@link Violation}s.
+ * For each classpath or source root a new {@link ViolationCollector} is
+ * created by the {@link ViolationCollectorFactory#create}. When the validation
+ * of the root is done the {@link ViolationCollector#finished} is called.
+ * Threading: Validations of individual roots may run in parallel depending
+ * on the {@link Executor} throughput.
+ */
+ public interface ViolationCollector {
+ /**
+ * Called to report a {@link Profile} violation.
+ * @param violation the {@link Violation} to be reported.
+ */
+ void reportProfileViolation(@NonNull Violation violation);
+ /**
+ * Called when the validation of whole a root has finished.
+ */
+ void finished();
+ }
+
+ /**
+ * Factory for {@link ViolationCollector}.
+ * For each root a new {@link ViolationCollector} is created by the factory.
+ * Threading: Validations of individual roots may run in parallel depending
+ * on the {@link Executor} throughput.
+ */
+ public interface ViolationCollectorFactory {
+ /**
+ * Creates a new {@lni ViolationCollector} for given root.
+ * @param root the root to be validated
+ * @return a new {@link ViolationCollector}
+ */
+ @NonNull
+ ViolationCollector create(@NonNull URL root);
+
+ /**
+ * Signals that the validation should be canceled.
+ * @return if true the validation is canceled.
+ */
+ boolean isCancelled();
+ }
+
+ /**
+ * Asynchronously finds the {@link Profile} violations in given source and classpath roots.
+ * @param profileToCheck the {@link Profile} to be verified
+ * @param bootClassPath the boot classpath of JDK 8 platform to get the profile info from
+ * @param compileClassPath the compile classpath to be validated
+ * @param sourcePath the source path to be validated
+ * @param check types of validation
+ * @param collectorFactory the {@link Violation}s collector
+ * @throws IllegalArgumentException if the bootClassPath is not a valid JDK 8 boot classpath
+ */
+ public static void findProfileViolations(
+ @NonNull final Profile profileToCheck,
+ @NonNull final Iterable bootClassPath,
+ @NonNull final Iterable compileClassPath,
+ @NonNull final Iterable sourcePath,
+ @NonNull final Set check,
+ @NonNull final ViolationCollectorFactory collectorFactory) {
+ findProfileViolations(
+ profileToCheck,
+ bootClassPath,
+ compileClassPath,
+ sourcePath,
+ check,
+ collectorFactory,
+ RP);
+ }
+
+ /**
+ * Synchronously finds the {@link Profile} violations in given source and classpath roots.
+ * @param profileToCheck the {@link Profile} to be verified
+ * @param bootClassPath the boot classpath of JDK 8 platform to get the profile info from
+ * @param compileClassPath the compile classpath to be validated
+ * @param sourcePath the source path to be validated
+ * @param check types of validation
+ * @return the {@link Collection} of found {@link Violation}s
+ * @throws IllegalArgumentException if the bootClassPath is not a valid JDK 8 boot classpath
+ */
+ @NonNull
+ public static Collection findProfileViolations(
+ @NonNull final Profile profileToCheck,
+ @NonNull final Iterable bootClassPath,
+ @NonNull final Iterable compileClassPath,
+ @NonNull final Iterable sourcePath,
+ @NonNull final Set check) {
+ final DefaultProfileViolationCollector collector =
+ new DefaultProfileViolationCollector();
+ findProfileViolations(
+ profileToCheck,
+ bootClassPath,
+ compileClassPath,
+ sourcePath,
+ check,
+ collector,
+ new CurrentThreadExecutor());
+ return collector.getViolations();
+ }
+
+ /**
+ * Asynchronously finds the {@link Profile} violations in given source and classpath roots.
+ * @param profileToCheck the {@link Profile} to be verified
+ * @param bootClassPath the boot classpath of JDK 8 platform to get the profile info from
+ * @param compileClassPath the compile classpath to be validated
+ * @param sourcePath the source path to be validated
+ * @param check types of validation
+ * @param collectorFactory the {@link Violation}s collector
+ * @param executor to use for the asynchronous operation, may have higher throughput
+ * @throws IllegalArgumentException if the bootClassPath is not a valid JDK 8 boot classpath
+ */
+ public static void findProfileViolations(
+ @NonNull final Profile profileToCheck,
+ @NonNull final Iterable bootClassPath,
+ @NonNull final Iterable compileClassPath,
+ @NonNull final Iterable sourcePath,
+ @NonNull final Set check,
+ @NonNull final ViolationCollectorFactory collectorFactory,
+ @NonNull final Executor executor) {
+ Parameters.notNull("profileToCheck", profileToCheck); //NOI18N
+ Parameters.notNull("compileClassPath", compileClassPath); //NOI18N
+ Parameters.notNull("sourcePath", sourcePath); //NOI18N
+ Parameters.notNull("check", check); //NOI18N
+ Parameters.notNull("collectorFactory", collectorFactory); //NOI18N
+ Parameters.notNull("executor", executor); //NOI18N
+ final Context ctx = new Context(profileToCheck, bootClassPath, collectorFactory, check);
+ if (check.contains(Validation.BINARIES_BY_MANIFEST) ||
+ check.contains(Validation.BINARIES_BY_CLASS_FILES)) {
+ for (final URL compileRoot : compileClassPath) {
+ executor.execute(Validator.forBinary(compileRoot, ctx));
+ }
+ }
+ if (check.contains(Validation.SOURCES)) {
+ for (final URL sourceRoot : sourcePath) {
+ executor.execute(Validator.forSource(sourceRoot, ctx));
+ }
+ }
+ }
+
+ private static final class Context {
+
+ private final ArchiveCache archiveCache;
+ private final TypeCache typeCache;
+ private final Profile profileToCheck;
+ private final ViolationCollectorFactory factory;
+ private final Set validations;
+
+ Context(
+ @NonNull final Profile profileToCheck,
+ @NonNull final Iterable extends URL> bootClassPath,
+ @NonNull final ViolationCollectorFactory factory,
+ @NonNull final Set validations) {
+ assert profileToCheck != null;
+ assert bootClassPath != null;
+ assert factory != null;
+ assert validations != null;
+ this.archiveCache = ArchiveCache.getInstance();
+ this.typeCache = TypeCache.newInstance(bootClassPath);
+ this.profileToCheck = profileToCheck;
+ this.factory = factory;
+ this.validations = EnumSet.copyOf(validations);
+ }
+
+ @NonNull
+ ArchiveCache getArchiveCache() {
+ return archiveCache;
+ }
+
+ @NonNull
+ TypeCache getTypeCache() {
+ return typeCache;
+ }
+
+ @NonNull
+ Profile getRequredProfile() {
+ return profileToCheck;
+ }
+
+ @NonNull
+ ViolationCollector newCollector(@NonNull final URL root) {
+ return factory.create(root);
+ }
+
+ boolean shouldValidate(@NonNull final Validation validation) {
+ return validations.contains(validation);
+ }
+
+ boolean isCancelled() {
+ return factory.isCancelled();
+ }
+
+ }
+
+ private static abstract class Validator implements Runnable {
+
+ protected final Context context;
+ protected final URL root;
+
+
+ Validator(
+ @NonNull final URL root,
+ @NonNull final Context context) {
+ assert root != null;
+ assert context != null;
+ this.root = root;
+ this.context = context;
+ }
+
+ @Override
+ public final void run() {
+ final ViolationCollector collector = context.newCollector(root);
+ assert collector != null;
+ try {
+ validate(collector);
+ } finally {
+ collector.finished();
+ }
+ }
+
+ protected final void validateBinaryRoot(
+ @NonNull final URL root,
+ @NonNull final ViolationCollector collector) {
+ final FileObject rootFo = URLMapper.findFileObject(root);
+ if (rootFo == null) {
+ return;
+ }
+ final Enumeration extends FileObject> children = rootFo.getChildren(true);
+ while (children.hasMoreElements()) {
+ final FileObject fo = children.nextElement();
+ if (isImportant(fo)) {
+ validateBinaryFile(fo, collector);
+ }
+ }
+ }
+
+ @NonNull
+ protected URL map(@NonNull final FileObject fo) {
+ return fo.toURL();
+ }
+
+ protected abstract void validate(@NonNull ViolationCollector collector);
+
+ private boolean isImportant(@NonNull final FileObject file) {
+ return file.isData() &&
+ (FileObjects.CLASS.equals(file.getExt()) || FileObjects.SIG.equals(file.getExt()));
+ }
+
+ private void validateBinaryFile(
+ @NonNull final FileObject fo,
+ @NonNull final ViolationCollector collector) {
+ final Profile profileToCheck = context.getRequredProfile();
+ final TypeCache tc = context.getTypeCache();
+ try {
+ try (InputStream in = fo.getInputStream()) {
+ ClassFile cf = new ClassFile(in);
+ for (ClassName className : cf.getAllClassNames()) {
+ final Profile p = tc.profileForType(className);
+ if (p != null && profileToCheck.compareTo(p) < 0) {
+ collector.reportProfileViolation(
+ new Violation(
+ root,
+ p,
+ map(fo),
+ ElementHandleAccessor.getInstance().create(ElementKind.CLASS, className.getInternalName().replace('/', '.')) //NOI18N
+ ));
+ }
+ }
+ }
+ } catch (IOException ioe) {
+ LOG.log(
+ Level.INFO,
+ "Cannot validate file: {0}", //NOI18N
+ FileUtil.getFileDisplayName(fo));
+ }
+ }
+
+ static Validator forSource(
+ @NonNull final URL root,
+ @NonNull final Context context) {
+ return new SourceValidator(root, context);
+ }
+
+ static Validator forBinary(
+ @NonNull final URL root,
+ @NonNull final Context context) {
+ return new BinaryValidator(root, context);
+ }
+
+ private final static class BinaryValidator extends Validator {
+
+ private BinaryValidator(
+ @NonNull final URL root,
+ @NonNull final Context context) {
+ super(root, context);
+ }
+
+ @Override
+ protected void validate(@NonNull final ViolationCollector collector) {
+ Profile current = null;
+ if (context.shouldValidate(Validation.BINARIES_BY_MANIFEST)) {
+ final Union2 res = findProfileInManifest(root);
+ if (!res.hasFirst()) {
+ //Invalid value of profile in manifest of dependent jar
+ collector.reportProfileViolation(new Violation(root, null, null, null));
+ return;
+ }
+ if (res.first().compareTo(context.getRequredProfile()) > 0) {
+ //Hiher profile in manifest of dependent jar
+ collector.reportProfileViolation(new Violation(root, res.first(), null, null));
+ return;
+ }
+ current = res.first();
+ }
+ if (context.shouldValidate(Validation.BINARIES_BY_CLASS_FILES)) {
+ if (current == null || current == Profile.DEFAULT) {
+ validateBinaryRoot(root, collector);
+ }
+ }
+ }
+
+ @NonNull
+ private Union2 findProfileInManifest(@NonNull URL root) {
+ final ArchiveCache ac = context.getArchiveCache();
+ Union2 res;
+ final ArchiveCache.Key key = ac.createKey(root);
+ if (key != null) {
+ res = ac.getProfile(key);
+ if (res != null) {
+ return res;
+ }
+ }
+ String profileName = null;
+ final FileObject rootFo = URLMapper.findFileObject(root);
+ if (rootFo != null) {
+ final FileObject manifestFile = rootFo.getFileObject(RES_MANIFEST);
+ if (manifestFile != null) {
+ try {
+ try (InputStream in = manifestFile.getInputStream()) {
+ final Manifest manifest = new Manifest(in);
+ final Attributes attrs = manifest.getMainAttributes();
+ profileName = attrs.getValue(ATTR_PROFILE);
+ }
+ } catch (IOException ioe) {
+ LOG.log(
+ Level.INFO,
+ "Cannot read Profile attribute from: {0}", //NOI18N
+ FileUtil.getFileDisplayName(manifestFile));
+ }
+ }
+ }
+ final Profile profile = Profile.forName(profileName);
+ res = profile != null ?
+ Union2.createFirst(profile) :
+ Union2.createSecond(profileName);
+ if (key != null) {
+ ac.putProfile(key, res);
+ }
+ return res;
+ }
+
+ }
+
+ private final static class SourceValidator extends Validator {
+ private SourceValidator(
+ @NonNull final URL root,
+ @NonNull final Context context) {
+ super(root, context);
+ }
+
+ @Override
+ protected void validate(@NonNull final ViolationCollector collector) {
+ try {
+ final File cacheRoot = JavaIndex.getClassFolder(root, true);
+ if (cacheRoot != null) {
+ validateBinaryRoot(Utilities.toURI(cacheRoot).toURL(), collector);
+ }
+ } catch (IOException e) {
+ Exceptions.printStackTrace(e);
+ }
+ }
+ }
+ }
+
+
+ private static class CurrentThreadExecutor implements Executor {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ }
+
+ private static class DefaultProfileViolationCollector implements ViolationCollectorFactory, ViolationCollector {
+
+ private final Queue violations = new ArrayDeque<>();
+
+ @Override
+ public ViolationCollector create(@NonNull final URL root) {
+ return this;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void reportProfileViolation(@NonNull final Violation violation) {
+ violations.offer(violation);
+ }
+
+ @Override
+ public void finished() {
+ }
+
+ Collection getViolations() {
+ return Collections.unmodifiableCollection(violations);
+ }
+ }
+
+ //@ThreadSafe
+ private static final class ArchiveCache {
+
+ private static final int MAX_CACHE_SIZE = Integer.getInteger(
+ "ProfileSupport.ArchiveCache.size", //NOI18N
+ 1<<10);
+
+ //@GuardedBy("ArchiveCache.class")
+ private static volatile ArchiveCache instance;
+
+ //@GuardedBy("cache")
+ private final Map> cache;
+
+ private ArchiveCache() {
+ this.cache = Collections.synchronizedMap(new LinkedHashMap>(16, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry> entry) {
+ return size() > MAX_CACHE_SIZE;
+ }
+ });
+ }
+
+ @NonNull
+ static ArchiveCache getInstance() {
+ ArchiveCache cache = instance;
+ if (cache == null) {
+ synchronized (ArchiveCache.class) {
+ cache = instance;
+ if (cache == null) {
+ instance = cache = new ArchiveCache();
+ }
+ }
+ }
+ return cache;
+ }
+
+ @CheckForNull
+ Union2 getProfile(@NonNull final Key key) {
+ final Union2 res = cache.get(key);
+ if (LOG.isLoggable(Level.FINER)) {
+ LOG.log(
+ Level.FINER,
+ "cache[{0}]->{1}", //NOI18N
+ new Object[]{
+ key,
+ res.hasFirst() ? res.first() : res.second()
+ });
+ }
+ return res;
+ }
+
+ void putProfile(
+ @NonNull final Key key,
+ @NonNull final Union2 profile) {
+ if (LOG.isLoggable(Level.FINER)) {
+ LOG.log(
+ Level.FINER,
+ "cache[{0}]<-{1}", //NOI18N
+ new Object[]{
+ key,
+ profile.hasFirst() ? profile.first() : profile.second()
+ });
+ }
+ cache.put(key,profile);
+ }
+
+ @CheckForNull
+ Key createKey(@NonNull final URL rootURL) {
+ final URL fileURL = FileUtil.getArchiveFile(rootURL);
+ if (fileURL == null) {
+ //Not an archive
+ return null;
+ }
+ final FileObject fileFo = URLMapper.findFileObject(fileURL);
+ if (fileFo == null) {
+ return null;
+ }
+ return new Key(
+ fileFo.toURI(),
+ fileFo.lastModified().getTime(),
+ fileFo.getSize());
+ }
+
+ private static final class Key {
+
+ private final URI root;
+ private final long mtime;
+ private final long size;
+
+ Key(
+ @NonNull final URI root,
+ final long mtime,
+ final long size) {
+ this.root = root;
+ this.mtime = mtime;
+ this.size = size;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + (this.root != null ? this.root.hashCode() : 0);
+ hash = 31 * hash + (int) (this.mtime ^ (this.mtime >>> 32));
+ hash = 31 * hash + (int) (this.size ^ (this.size >>> 32));
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Key)) {
+ return false;
+ }
+ final Key other = (Key) obj;
+ return this.root.equals(other.root) &&
+ this.mtime == other.mtime &&
+ this.size == other.size;
+
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Key{root: %s, mtime: %d, size: %d}", //NOI18N
+ root,
+ mtime,
+ size);
+ }
+ }
+ }
+
+ //@ThreadSafe
+ private static final class TypeCache {
+
+ private final Object UNKNOWN = new Object();
+ private final ConcurrentMap cache;
+ private final Archive ctSym;
+
+ private TypeCache(@NonNull final Archive ctSym) {
+ assert ctSym != null;
+ this.ctSym = ctSym;
+ cache = new ConcurrentHashMap<>();
+ }
+
+ @NonNull
+ static TypeCache newInstance(Iterable extends URL> bootClassPath) {
+ Archive ctSym = null;
+ final CachingArchiveProvider ap = CachingArchiveProvider.getDefault();
+ for (URL root : bootClassPath) {
+ if (ap.hasCtSym(root)) {
+ ctSym = ap.getArchive(root, true);
+ break;
+ }
+ }
+ if (ctSym == null) {
+ throw new IllegalArgumentException(
+ String.format(
+ "No profile info for boot classpath: %s", //NOI18N
+ bootClassPath));
+ }
+ return new TypeCache(ctSym);
+ }
+
+ @CheckForNull
+ Profile profileForType(@NonNull final ClassName className) {
+ final String binName = className.getInternalName();
+ Object res = cache.get(binName);
+ if (res == null) {
+ res = findProfile(binName);
+ cache.put(binName, res);
+ }
+ return res == UNKNOWN ? null : (Profile) res;
+ }
+
+ @NonNull
+ private Object findProfile(@NonNull final String binaryName) {
+ Object res = UNKNOWN;
+ final StringBuilder sb = new StringBuilder(binaryName);
+ sb.append('.'); //NOI18N
+ sb.append(FileObjects.CLASS);
+ try {
+ final JavaFileObject jfo = ctSym.getFile(sb.toString());
+ if (jfo != null) {
+ try (InputStream in = jfo.openInputStream()) {
+ final ClassFile cf = new ClassFile(in);
+ final Annotation a = cf.getAnnotation(ClassName.getClassName(ANNOTATION_PROFILE));
+ if (a == null) {
+ res = Profile.COMPACT1;
+ } else {
+ final AnnotationComponent ac = a.getComponent(ANNOTATION_VALUE);
+ res = profileFromAnnotationComponent(ac);
+ }
+ }
+ }
+ } catch (IOException ioe) {
+ Exceptions.printStackTrace(ioe);
+ }
+ return res;
+ }
+
+ @NonNull
+ private static Profile profileFromAnnotationComponent(@NullAllowed final AnnotationComponent ac) {
+ if (ac == null) {
+ return Profile.COMPACT1;
+ }
+ try {
+ final ElementValue ev = ac.getValue();
+ if (!(ev instanceof PrimitiveElementValue)) {
+ return Profile.COMPACT1;
+ }
+ final CPEntry cpEntry = ((PrimitiveElementValue)ev).getValue();
+ if (cpEntry.getTag() != 3) {
+ return Profile.COMPACT1;
+ }
+ final int ordinal = (Integer) cpEntry.getValue();
+ if (ordinal <= 0) {
+ return Profile.COMPACT1;
+ }
+ final Profile[] values = Profile.values();
+ if (ordinal >= values.length) {
+ return Profile.DEFAULT;
+ }
+ return values[ordinal-1];
+ } catch (NumberFormatException nfe) {
+ return Profile.COMPACT1;
+ }
+ }
+ }
+
+}
--- a/java.source/src/org/netbeans/modules/java/source/parsing/CachingArchiveProvider.java
+++ a/java.source/src/org/netbeans/modules/java/source/parsing/CachingArchiveProvider.java
@@ -161,6 +161,12 @@
}
}
+ /**
+ * Maps ct.sym back to the base boot classpath root.
+ * @param archiveOrCtSym the root or ct.sym folder to transform.
+ * @return the boot classpath root corresponding to the ct.sym folder
+ * or the given boot classpath root.
+ */
@NonNull
public URL mapCtSymToJar (@NonNull final URL archiveOrCtSym) {
final URI result = ctSymToJar.get(toURI(archiveOrCtSym));
@@ -175,6 +181,28 @@
}
/**
+ * Checks if the given boot classpath root has the the ct.sym equivalent.
+ * @param root the root to check
+ * @return true if there is a ct.sym folder corresponding to given boot classpath
+ * root
+ */
+ public boolean hasCtSym (@NonNull final URL root) {
+ final URL fileURL = FileUtil.getArchiveFile(root);
+ if (fileURL == null) {
+ return false;
+ }
+ final File f = Utilities.toFile(fileURL);
+ if (f == null || !f.exists()) {
+ return false;
+ }
+ synchronized (this) {
+ final Pair res = mapJarToCtSym(f, root);
+ return res.second != null;
+ }
+
+ }
+
+ /**
* Frees all the archives.
*/
synchronized void clear() {
--- a/java.source/test/unit/src/org/netbeans/api/java/source/support/ProfileSupportTest.java
+++ a/java.source/test/unit/src/org/netbeans/api/java/source/support/ProfileSupportTest.java
@@ -0,0 +1,411 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.api.java.source.support;
+
+
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaCompiler;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.platform.JavaPlatform;
+import org.netbeans.api.java.platform.JavaPlatformManager;
+import org.netbeans.api.java.platform.Specification;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.junit.MockServices;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.java.platform.JavaPlatformProvider;
+import org.netbeans.modules.java.source.ElementHandleAccessor;
+import org.netbeans.modules.java.source.parsing.FileObjects;
+import org.netbeans.modules.java.source.usages.Pair;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.SpecificationVersion;
+import org.openide.util.Utilities;
+
+/**
+ *
+ * @author Tomas Zezula
+ */
+public class ProfileSupportTest extends NbTestCase {
+
+ private static final String JDK8Home = "/Users/tom/Projects/other/repos/jdk/jdk8/build/macosx-x86_64-normal-server-release/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home";
+
+ public ProfileSupportTest(@NonNull final String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ clearWorkDir();
+ MockServices.setServices(PP.class);
+ }
+
+
+ public void testProfilesByClassFiles() throws IOException {
+ final JavaPlatform jp = JavaPlatformManager.getDefault().getDefaultPlatform();
+ if (jp == null || !jp.getSpecification().getVersion().equals(new SpecificationVersion("1.8"))) { //NOI18N
+ //Nothing to test
+ return;
+ }
+ final Map> testCp = createTestData();
+ Collection res = ProfileSupport.findProfileViolations(
+ SourceLevelQuery.Profile.COMPACT1,
+ toURLs(jp.getBootstrapLibraries().entries()),
+ testCp.keySet(),
+ Collections.emptySet(),
+ EnumSet.of(ProfileSupport.Validation.BINARIES_BY_CLASS_FILES));
+ assertEquals(
+ new HashMap,SourceLevelQuery.Profile>>>(){
+ {
+ final Collection files = testCp.entrySet().iterator().next().getValue();
+ put(
+ files.iterator().next(),
+ Arrays.,SourceLevelQuery.Profile>>asList(
+ Pair.,SourceLevelQuery.Profile>of(
+ ElementHandleAccessor.getInstance().create(ElementKind.CLASS, "java.sql.Date"),
+ SourceLevelQuery.Profile.COMPACT2),
+ Pair.,SourceLevelQuery.Profile>of(
+ ElementHandleAccessor.getInstance().create(ElementKind.CLASS, "javax.naming.Context"),
+ SourceLevelQuery.Profile.COMPACT3),
+ Pair.,SourceLevelQuery.Profile>of(
+ ElementHandleAccessor.getInstance().create(ElementKind.CLASS, "org.omg.CORBA.ORB"),
+ SourceLevelQuery.Profile.DEFAULT)
+ ));
+ }
+ },
+ res);
+
+ res = ProfileSupport.findProfileViolations(
+ SourceLevelQuery.Profile.COMPACT2,
+ toURLs(jp.getBootstrapLibraries().entries()),
+ testCp.keySet(),
+ Collections.emptySet(),
+ EnumSet.of(ProfileSupport.Validation.BINARIES_BY_CLASS_FILES));
+ assertEquals(
+ new HashMap,SourceLevelQuery.Profile>>>(){
+ {
+ final Collection files = testCp.entrySet().iterator().next().getValue();
+ put(
+ files.iterator().next(),
+ Arrays.,SourceLevelQuery.Profile>>asList(
+ Pair.,SourceLevelQuery.Profile>of(
+ ElementHandleAccessor.getInstance().create(ElementKind.CLASS, "javax.naming.Context"),
+ SourceLevelQuery.Profile.COMPACT3),
+ Pair.,SourceLevelQuery.Profile>of(
+ ElementHandleAccessor.getInstance().create(ElementKind.CLASS, "org.omg.CORBA.ORB"),
+ SourceLevelQuery.Profile.DEFAULT)
+ ));
+ }
+ },
+ res);
+ res = ProfileSupport.findProfileViolations(
+ SourceLevelQuery.Profile.COMPACT3,
+ toURLs(jp.getBootstrapLibraries().entries()),
+ testCp.keySet(),
+ Collections.emptySet(),
+ EnumSet.of(ProfileSupport.Validation.BINARIES_BY_CLASS_FILES));
+ assertEquals(
+ new HashMap,SourceLevelQuery.Profile>>>(){
+ {
+ final Collection files = testCp.entrySet().iterator().next().getValue();
+ put(
+ files.iterator().next(),
+ Arrays.,SourceLevelQuery.Profile>>asList(
+ Pair.,SourceLevelQuery.Profile>of(
+ ElementHandleAccessor.getInstance().create(ElementKind.CLASS, "org.omg.CORBA.ORB"),
+ SourceLevelQuery.Profile.DEFAULT)
+ ));
+ }
+ },
+ res);
+ res = ProfileSupport.findProfileViolations(
+ SourceLevelQuery.Profile.DEFAULT,
+ toURLs(jp.getBootstrapLibraries().entries()),
+ testCp.keySet(),
+ Collections.emptySet(),
+ EnumSet.of(ProfileSupport.Validation.BINARIES_BY_CLASS_FILES));
+ assertEquals(
+ Collections.,SourceLevelQuery.Profile>>>emptyMap(),
+ res);
+ }
+
+ private void assertEquals(
+ Map,SourceLevelQuery.Profile>>> exp,
+ Collection res) {
+ final Map,SourceLevelQuery.Profile>>> resM =
+ new HashMap<>();
+ for (ProfileSupport.Violation v : res) {
+ List,SourceLevelQuery.Profile>> e = resM.get(v.getFile());
+ if (e == null) {
+ e = new ArrayList<>();
+ resM.put(v.getFile(), e);
+ }
+ e.add(Pair.,SourceLevelQuery.Profile>of(
+ v.getUsedType(),
+ v.getRequiredProfile()));
+ }
+ for (List,SourceLevelQuery.Profile>> l : resM.values()) {
+ Collections.sort(l, new Comparator,SourceLevelQuery.Profile>>() {
+ @Override
+ public int compare(
+ Pair, SourceLevelQuery.Profile> o1,
+ Pair, SourceLevelQuery.Profile> o2) {
+ return o1.first.getBinaryName().compareTo(o2.first.getBinaryName());
+ }
+ });
+ }
+ assertEquals(exp, resM);
+ };
+
+
+ @NonNull
+ private Collection toURLs(@NonNull final Collection extends ClassPath.Entry> entries) {
+ final Queue q = new ArrayDeque<>();
+ for (ClassPath.Entry e : entries) {
+ q.offer(e.getURL());
+ }
+ return q;
+ }
+
+ private Map> createTestData() throws IOException {
+ final File workDir = getWorkDir();
+ final File src = new File (workDir, "src"); //NOI18N
+ final File bin = new File (workDir, "bin"); //NOI18N
+ src.mkdirs();
+ bin.mkdirs();
+ final File testFile = new File (src,"Test.java"); //NOI18N
+ try (PrintWriter out = new PrintWriter(testFile)) {
+ out.println(
+ "public class Test {\n" + //NOI18N
+ " String s;\n" + //NOI18N
+ " java.sql.Date d;\n" + //NOI18N
+ " javax.naming.Context c;\n" +//NOI18N
+ " org.omg.CORBA.ORB orb;\n" +//NOI18N
+ "}\n"); //NOI18N
+ }
+ final JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
+ final StandardJavaFileManager standardFileManager = jc.getStandardFileManager(null, null, null);
+ final StringWriter out = new StringWriter();
+ final JavaCompiler.CompilationTask task = jc.getTask(
+ out,
+ standardFileManager,
+ null,
+ Arrays.asList(
+ "-s", src.getAbsolutePath(), //NOI18N
+ "-d", bin.getAbsolutePath(), //NOI18N
+ "-target", "1.8", //NOI18N
+ "-source", "1.8", //NOI18N
+ "-processorpath", ""), //NOI18N
+ Collections.emptySet(),
+ standardFileManager.getJavaFileObjects(testFile));
+ final Boolean res = task.call();
+ if (res != Boolean.TRUE) {
+ throw new IOException(out.toString());
+ }
+ return Collections.>singletonMap(
+ Utilities.toURL(bin),
+ collectClassFiles(bin));
+ }
+
+ @NonNull
+ private Collection collectClassFiles(@NonNull final File folder) throws MalformedURLException {
+ final Queue q = new ArrayDeque<>();
+ collectClassFiles(folder, q);
+ return q;
+ }
+
+ private void collectClassFiles(@NonNull final File folder, @NonNull final Queue super URL> q) throws MalformedURLException {
+ final File[] chld = folder.listFiles();
+ if (chld == null) {
+ return;
+ }
+ for (File f : chld) {
+ if (f.isDirectory()) {
+ collectClassFiles(f, q);
+ } else if (FileObjects.CLASS.equals(FileObjects.getExtension(f.getName()))) {
+ q.offer(Utilities.toURL(f));
+ }
+ }
+ }
+
+
+ public static class PP implements JavaPlatformProvider {
+
+ private final JavaPlatform jp;
+
+ public PP () {
+ final Pair jdk8 = findJDK8();
+ jp = jdk8 == null ? null : new JP(jdk8);
+ }
+
+
+ @Override
+ public JavaPlatform[] getInstalledPlatforms() {
+ return jp == null ?
+ new JavaPlatform[0] :
+ new JavaPlatform[] {jp};
+ }
+
+ @Override
+ public JavaPlatform getDefaultPlatform() {
+ return jp;
+ }
+
+ @Override
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ }
+
+ @Override
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ }
+
+ @CheckForNull
+ private static Pair findJDK8() {
+ File jdkHome = null;
+ if (JDK8Home != null) {
+ jdkHome = new File(JDK8Home);
+ } else {
+ //TODO:
+ }
+ if (jdkHome == null || !jdkHome.isDirectory()) {
+ return null;
+ }
+ final File rtJar = new File(
+ jdkHome,
+ "jre/lib/rt.jar".replace('/', File.separatorChar)); //NOI18N
+ if (!rtJar.isFile()) {
+ return null;
+ }
+ return Pair.of(
+ FileUtil.normalizeFile(jdkHome),
+ FileUtil.urlForArchiveOrDir(FileUtil.normalizeFile(rtJar)));
+ }
+
+ private static class JP extends JavaPlatform {
+
+ private final FileObject home;
+ private final ClassPath boot;
+
+
+ JP(Pair p) {
+ this.home = FileUtil.toFileObject(p.first);
+ this.boot = ClassPathSupport.createClassPath(p.second);
+ }
+
+
+ @Override
+ public String getDisplayName() {
+ return "Mock Java Platform"; //NOI18N
+ }
+
+ @Override
+ public Map getProperties() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public ClassPath getBootstrapLibraries() {
+ return boot;
+ }
+
+ @Override
+ public ClassPath getStandardLibraries() {
+ return ClassPath.EMPTY;
+ }
+
+ @Override
+ public String getVendor() {
+ return "me"; //NOI18N
+ }
+
+ @Override
+ public Specification getSpecification() {
+ return new Specification("j2se", new SpecificationVersion("1.8")); //NOI18N
+ }
+
+ @Override
+ public Collection getInstallFolders() {
+ return Collections.singleton(home);
+ }
+
+ @Override
+ public FileObject findTool(String toolName) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public ClassPath getSourceFolders() {
+ return ClassPath.EMPTY;
+ }
+
+ @Override
+ public List getJavadocFolders() {
+ return Collections.emptyList();
+ }
+ }
+ }
+}