diff --git a/java.j2seproject/nbproject/project.xml b/java.j2seproject/nbproject/project.xml --- a/java.j2seproject/nbproject/project.xml +++ b/java.j2seproject/nbproject/project.xml @@ -159,7 +159,7 @@ 1 - 1.48 + 1.53 diff --git a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java +++ b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java @@ -406,6 +406,7 @@ WhiteListQueryMergerSupport.createWhiteListQueryMerger(), BrokenReferencesSupport.createReferenceProblemsProvider(helper, refHelper, eval, lvp.getBreakableProperties(), lvp.getPlatformProperties()), BrokenReferencesSupport.createPlatformVersionProblemProvider(helper, eval, new PlatformChangedHook(), JavaPlatform.getDefault().getSpecification().getName(), J2SEProjectProperties.JAVA_PLATFORM, J2SEProjectProperties.JAVAC_SOURCE, J2SEProjectProperties.JAVAC_TARGET), + BrokenReferencesSupport.createProfileProblemProvider(helper, refHelper, eval, J2SEProjectProperties.JAVAC_PROFILE, ProjectProperties.JAVAC_CLASSPATH, ProjectProperties.RUN_CLASSPATH, ProjectProperties.ENDORSED_CLASSPATH), UILookupMergerSupport.createProjectProblemsProviderMerger() ); lookup = base; // in case LookupProvider's call Project.getLookup diff --git a/java.project/apichanges.xml b/java.project/apichanges.xml --- a/java.project/apichanges.xml +++ b/java.project/apichanges.xml @@ -109,6 +109,22 @@ + + + Added ProjectProblemsProvider to resolve JDK 8 Profile problems + + + + + +

+ Added a factory method createProfileProblemProvider into BrokenReferencesSupport + creating a ProjectProblemsProvider resolving dependencies on libraries with higher or invalid profile. +

+
+ + +
Changed BrokenReferencesSupport to delegate to ProjectProblems diff --git a/java.project/manifest.mf b/java.project/manifest.mf --- a/java.project/manifest.mf +++ b/java.project/manifest.mf @@ -3,7 +3,7 @@ OpenIDE-Module-Layer: org/netbeans/modules/java/project/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/java/project/Bundle.properties OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker -OpenIDE-Module-Specification-Version: 1.52 +OpenIDE-Module-Specification-Version: 1.53 OpenIDE-Module-Recommends: org.netbeans.spi.java.project.runner.JavaRunnerImplementation AutoUpdate-Show-In-Client: false diff --git a/java.project/src/org/netbeans/modules/java/project/Bundle.properties b/java.project/src/org/netbeans/modules/java/project/Bundle.properties --- a/java.project/src/org/netbeans/modules/java/project/Bundle.properties +++ b/java.project/src/org/netbeans/modules/java/project/Bundle.properties @@ -107,3 +107,12 @@ FixProjectSourceLevel.useOtherPlatform.text=Use Other Java &Platform FixProjectSourceLevel.managePlatforms.text=&Manage Platforms... FixProjectSourceLevel.jLabel2.text=The project source/binary format requires a newer java platform ({0}). +LBL_FixProfile_BrokenLibs=&The following project libraries have invalid value of profile: +LBL_FixProfile_remove=&Remove +TIP_FixProfile_Remove=Remove library from project +LBL_FixProfile_changeProfile=Change &Project Profile To: +AD_FixProfile_BrokenLibs=The libraries with higher or invalid value of JRE profile. +AD_FixProfile_changeProfile=Update project profile. +FixProfile.jLabel1.AccessibleContext.accessibleName=&The following project libraries have invalid value of profile: +AN_FixProfile_Profiles=Profiles +AD_FixProfile_Profiles=Profiles diff --git a/java.project/src/org/netbeans/modules/java/project/FixProfile.form b/java.project/src/org/netbeans/modules/java/project/FixProfile.form new file mode 100644 --- /dev/null +++ b/java.project/src/org/netbeans/modules/java/project/FixProfile.form @@ -0,0 +1,141 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/java.project/src/org/netbeans/modules/java/project/FixProfile.java b/java.project/src/org/netbeans/modules/java/project/FixProfile.java new file mode 100644 --- /dev/null +++ b/java.project/src/org/netbeans/modules/java/project/FixProfile.java @@ -0,0 +1,492 @@ +/* + * 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.modules.java.project; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.AbstractListModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JButton; +import javax.swing.JList; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.openide.filesystems.FileUtil; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; + +/** + * + * @author Tomas Zezula + */ +public class FixProfile extends javax.swing.JPanel { + + private static final Pattern PROFILE_PATTERN = Pattern.compile("compact([123])"); //NOI18N + private static final String PROFILE_FORMAT = "compact%d"; //NOI18N + private static final int FULL_JRE = 4; + + private final JButton okOption; + private final LibsModel libsModel; + private String reqProfile; + + /** + * Creates new form FixProfile + */ + FixProfile( + @NonNull final JButton okOption, + @NonNull final String currentProfile, + @NonNull final Map> state) { + assert okOption != null; + assert currentProfile != null; + assert state != null; + this.okOption = okOption; + this.libsModel = new LibsModel (currentProfile, state); + libsModel.addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent lde) { + checkOkOption(); + } + + @Override + public void intervalRemoved(ListDataEvent lde) { + checkOkOption(); + } + + @Override + public void contentsChanged(ListDataEvent lde) { + checkOkOption(); + } + }); + this.reqProfile = requiredProfile(state); + initComponents(); + remove.setEnabled(false); + brokenLibs.setModel(libsModel); + brokenLibs.setCellRenderer(new LibsRenderer()); + brokenLibs.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + brokenLibs.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent lse) { + remove.setEnabled(brokenLibs.getSelectedIndex() != -1); + } + }); + brokenLibs.setSelectedIndex(0); + profiles.setRenderer(new ProfilesRenderer()); + changeProfile.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent ae) { + libsModel.updateProfile(changeProfile.isSelected()); + checkOkOption(); + } + }); + updateProfiles(); + checkOkOption(); + } + + boolean shouldUpdateProfile() { + return changeProfile.isSelected(); + } + + @CheckForNull + String getProfile() { + final Object selObj = profiles.getSelectedItem(); + if (selObj instanceof Integer) { + return getProfileName((Integer)selObj); + } else { + return null; + } + } + + @NonNull + Collection getRootsToRemove() { + return libsModel.getRemovedRooots(); + } + + @CheckForNull + static String normalize(@NullAllowed String profile) { + return profile != null && PROFILE_PATTERN.matcher(profile).matches() ? + profile : + null; + } + + @CheckForNull + static String requiredProfile(@NonNull final Map> state) { + String current = null; + for (Map.Entry> cpe : state.entrySet()) { + current = requiredProfile(cpe.getValue().entrySet(), current); + } + return current; + } + + @CheckForNull + private static String requiredProfile( + @NonNull final Collection> state, + @NullAllowed String current) { + for (Map.Entry re : state) { + current = max(current, re.getValue()); + } + return current; + } + + @CheckForNull + private static String max( + @NullAllowed String profileA, + @NullAllowed String profileB) { + profileA = normalize(profileA); + profileB = normalize(profileB); + if (profileA == null) { + return profileB; + } + if (profileB == null) { + return profileA; + } + return profileA.compareTo(profileB) < 0 ? + profileB : + profileA; + } + + private static int getProfileIndex(@NullAllowed final String profileName) { + if (profileName != null) { + final Matcher m = PROFILE_PATTERN.matcher(profileName); + if (m.matches()) { + return Integer.parseInt(m.group(1)); + } + } + return FULL_JRE; + } + + @CheckForNull + private static String getProfileName(final int index) { + if (index <= 0 || index > FULL_JRE) { + throw new IndexOutOfBoundsException( + String.format( + "Index: %d", //NOI18N + index)); + } + if (index == FULL_JRE) { + return null; + } + return String.format(PROFILE_FORMAT,index); + } + + private void updateProfiles() { + profiles.removeAllItems(); + final int required = getProfileIndex(reqProfile); + for (int i = required; i<= FULL_JRE; i++) { + profiles.addItem(i); + } + profiles.setSelectedItem(required); + } + + private void checkOkOption() { + okOption.setEnabled(libsModel.getSize() == 0); + } + + private static final class ProfilesRenderer extends DefaultListCellRenderer { + @Override + @NbBundle.Messages({ + "NAME_Compact1=Compact 1", + "NAME_Compact2=Compact 2", + "NAME_Compact3=Compact 3", + "NAME_FullJRE=Full JRE" + }) + public Component getListCellRendererComponent( + @NonNull final JList jlist, + @NullAllowed Object o, + final int i, + final boolean bln, + final boolean bln1) { + if (o instanceof Integer) { + switch ((Integer)o) { + case 1: + o = Bundle.NAME_Compact1(); + break; + case 2: + o = Bundle.NAME_Compact2(); + break; + case 3: + o = Bundle.NAME_Compact3(); + break; + case FULL_JRE: + o = Bundle.NAME_FullJRE(); + break; + default: + throw new IllegalArgumentException(o.toString()); + } + } + return super.getListCellRendererComponent(jlist, o, i, bln, bln1); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({ + "FMT_RootWithProfile={0} ({1})" + }) + private static final class LibsRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent( + @NonNull final JList jlist, + @NullAllowed Object o, + final int i, + final boolean bln, + final boolean bln1) { + String toolTip = null; + if (o instanceof Map.Entry) { + final Map.Entry e = (Map.Entry) o; + final String profile = e.getValue(); + String simpleName; + try { + final URL root = e.getKey().toURL(); + URL fileURL = FileUtil.getArchiveFile(root); + if (fileURL == null) { + fileURL = root; + } + final File f = Utilities.toFile(fileURL.toURI()); + simpleName = f.getName(); + toolTip = f.getAbsolutePath(); + } catch (URISyntaxException ex) { + simpleName = e.getKey().toString(); + } catch (MalformedURLException ex) { + simpleName = e.getKey().toString(); + } + o = Bundle.FMT_RootWithProfile(simpleName, profile); + } + final Component res = super.getListCellRendererComponent(jlist, o, i, bln, bln1); //To change body of generated methods, choose Tools | Templates. + setToolTipText(toolTip); + return res; + } + } + + private static final class LibsModel extends AbstractListModel { + + private final String currentProfile; + private final Map> state; + private final Set toRemove; + private final List> data; + private boolean updated; + + LibsModel( + @NonNull final String currentProfile, + @NonNull final Map> state) { + this.currentProfile = currentProfile; + this.state = state; + this.toRemove = new HashSet(); + this.data = new ArrayList>(); + refresh(); + } + + @Override + public int getSize() { + return data.size(); + } + + @Override + public Object getElementAt(int i) { + if (i<0 || i>=data.size()) { + throw new IndexOutOfBoundsException( + String.format( + "Index: %d, Size: %d", //NOI18N + i, + data.size())); + } + return data.get(i); + } + + void removeRoots(@NonNull final Collection root) { + final int oldSize = getSize(); + toRemove.addAll(root); + refresh(); + final int newSize = getSize(); + fireContentsChanged(this, 0, Math.max(oldSize, newSize)); + }; + + @CheckForNull + String requiredProfile() { + return FixProfile.requiredProfile(data, currentProfile); + } + + void updateProfile(final boolean update) { + final int oldSize = getSize(); + updated = update; + refresh(); + final int newSize = getSize(); + fireContentsChanged(this, 0, Math.max(oldSize, newSize)); + } + + @NonNull + Collection getRemovedRooots() { + return Collections.unmodifiableCollection(toRemove); + } + + private void refresh() { + final Set> newData = new HashSet>(); + for (Map.Entry> cpe : state.entrySet()) { + for (Map.Entry re : cpe.getValue().entrySet()) { + if (!toRemove.contains(re.getKey()) && + !(updated && PROFILE_PATTERN.matcher(re.getValue()).matches())) { + newData.add(re); + } + } + } + data.clear(); + data.addAll(newData); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + remove = new javax.swing.JButton(); + changeProfile = new javax.swing.JCheckBox(); + profiles = new javax.swing.JComboBox(); + jScrollPane1 = new javax.swing.JScrollPane(); + brokenLibs = new javax.swing.JList(); + + jLabel1.setLabelFor(brokenLibs); + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(FixProfile.class, "LBL_FixProfile_BrokenLibs")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(remove, org.openide.util.NbBundle.getMessage(FixProfile.class, "LBL_FixProfile_remove")); // NOI18N + remove.setToolTipText(org.openide.util.NbBundle.getMessage(FixProfile.class, "TIP_FixProfile_Remove")); // NOI18N + remove.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + removeLibrary(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(changeProfile, org.openide.util.NbBundle.getMessage(FixProfile.class, "LBL_FixProfile_changeProfile")); // NOI18N + + jScrollPane1.setViewportView(brokenLibs); + brokenLibs.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(FixProfile.class, "AD_FixProfile_BrokenLibs")); // NOI18N + + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(jLabel1) + .addContainerGap(154, Short.MAX_VALUE)) + .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) + .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup() + .add(changeProfile) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(profiles, 0, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .add(jScrollPane1)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(remove) + .addContainerGap()))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(jLabel1) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(remove) + .add(0, 0, Short.MAX_VALUE)) + .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 212, Short.MAX_VALUE)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(changeProfile) + .add(profiles, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + jLabel1.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(FixProfile.class, "FixProfile.jLabel1.AccessibleContext.accessibleName")); // NOI18N + changeProfile.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(FixProfile.class, "AD_FixProfile_changeProfile")); // NOI18N + profiles.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(FixProfile.class, "AN_FixProfile_Profiles")); // NOI18N + profiles.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(FixProfile.class, "AD_FixProfile_Profiles")); // NOI18N + }// //GEN-END:initComponents + + private void removeLibrary(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeLibrary + final Object[] selection = brokenLibs.getSelectedValues(); + final Set rootsToRemove = new HashSet(); + for (Object e : selection) { + rootsToRemove.add(((Map.Entry)e).getKey()); + } + libsModel.removeRoots(rootsToRemove); + if (libsModel.getSize() > 0) { + brokenLibs.setSelectedIndex(0); + } + reqProfile = libsModel.requiredProfile(); + updateProfiles(); + }//GEN-LAST:event_removeLibrary + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JList brokenLibs; + private javax.swing.JCheckBox changeProfile; + private javax.swing.JLabel jLabel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JComboBox profiles; + private javax.swing.JButton remove; + // End of variables declaration//GEN-END:variables +} diff --git a/java.project/src/org/netbeans/modules/java/project/ProjectProblemsProviders.java b/java.project/src/org/netbeans/modules/java/project/ProjectProblemsProviders.java --- a/java.project/src/org/netbeans/modules/java/project/ProjectProblemsProviders.java +++ b/java.project/src/org/netbeans/modules/java/project/ProjectProblemsProviders.java @@ -45,6 +45,7 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.net.URI; @@ -68,18 +69,25 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.Attributes; +import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.swing.JButton; import javax.swing.JFileChooser; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; 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.classpath.ClassPath; import org.netbeans.api.java.platform.JavaPlatform; import org.netbeans.api.java.platform.JavaPlatformManager; import org.netbeans.api.java.platform.PlatformsCustomizer; import org.netbeans.api.java.platform.Specification; +import org.netbeans.api.java.queries.SourceLevelQuery; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; @@ -99,6 +107,8 @@ import org.openide.util.Parameters; import static org.netbeans.modules.java.project.Bundle.*; +import org.netbeans.spi.java.classpath.ClassPathFactory; +import org.netbeans.spi.java.project.classpath.support.ProjectClassPathSupport; import org.netbeans.spi.java.project.support.ui.BrokenReferencesSupport; import org.netbeans.spi.project.support.ant.ui.VariablesSupport; import org.netbeans.spi.project.ui.ProjectProblemResolver; @@ -107,10 +117,12 @@ import org.netbeans.spi.project.ui.support.ProjectProblemsProviderSupport; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; +import org.openide.filesystems.FileUtil; import org.openide.modules.SpecificationVersion; import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.Mutex; +import org.openide.util.MutexException; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; import org.openide.util.WeakListeners; @@ -129,7 +141,7 @@ } - + @NonNull public static ProjectProblemsProvider createReferenceProblemProvider( @NonNull final AntProjectHelper projectHelper, @NonNull final ReferenceHelper referenceHelper, @@ -141,6 +153,7 @@ return pp; } + @NonNull public static ProjectProblemsProvider createPlatformVersionProblemProvider( @NonNull final AntProjectHelper helper, @NonNull final PropertyEvaluator evaluator, @@ -159,6 +172,22 @@ return pp; } + @NonNull + public static ProjectProblemsProvider createProfileProblemProvider( + @NonNull final AntProjectHelper antProjectHelper, + @NonNull final ReferenceHelper refHelper, + @NonNull final PropertyEvaluator evaluator, + @NonNull final String profileProperty, + @NonNull final String... classPathProperties) { + final ProfileProjectProviderImpl pp = new ProfileProjectProviderImpl( + antProjectHelper, + refHelper, + evaluator, + profileProperty, + classPathProperties); + return pp; + } + // @NonNull private static Set getReferenceProblems( @@ -955,6 +984,90 @@ } + + static final class ProfileResolver implements ProjectProblemResolver { + + private final AntProjectHelper antProjectHelper; + private final String profileProperty; + private final String currentProfile; + private final Map> state; + + private ProfileResolver( + @NonNull final AntProjectHelper antProjectHelper, + @NonNull final String profileProperty, + @NonNull final String currentProfile, + @NonNull final Map> state) { + assert antProjectHelper != null; + assert profileProperty != null; + assert currentProfile != null; + assert state != null; + this.antProjectHelper = antProjectHelper; + this.profileProperty = profileProperty; + this.currentProfile = currentProfile; + this.state = state; + } + + + @Override + @NbBundle.Messages({ + "LBL_ResolveProfile=Resolve Invalid Project Profile", + "LBL_ResolveButton=OK", + "AN_ResolveButton=Resolve", + "AD_ResolveButton=Resovle the profile problems" + }) + public Future resolve() { + final JButton ok = new JButton(LBL_ResolveButton()); + ok.getAccessibleContext().setAccessibleName(AN_ResolveButton()); + ok.getAccessibleContext().setAccessibleDescription(AD_ResolveButton()); + final FixProfile panel = new FixProfile(ok, currentProfile, state); + final DialogDescriptor dd = new DialogDescriptor( + panel, + LBL_ResolveProfile(), + true, + new Object[] { + ok, + DialogDescriptor.CANCEL_OPTION + }, + ok, + DialogDescriptor.DEFAULT_ALIGN, + null, + null); + if (DialogDisplayer.getDefault().notify(dd) == ok) { + return RP.submit(new Callable() { + @Override + public ProjectProblemsProvider.Result call() throws Exception { + ProjectProblemsProvider.Status status = ProjectProblemsProvider.Status.UNRESOLVED; + try { + ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction() { + @Override + public Void run() throws IOException { + final boolean shouldUpdate = panel.shouldUpdateProfile(); + if (shouldUpdate) { + final String newProfile = panel.getProfile(); + final EditableProperties props = antProjectHelper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH); + if (newProfile == null) { + props.remove(profileProperty); + } else { + props.put(profileProperty, newProfile); + } + antProjectHelper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, props); + } + ProjectManager.getDefault().saveProject( + FileOwnerQuery.getOwner(antProjectHelper.getProjectDirectory())); + return null; + } + }); + status = ProjectProblemsProvider.Status.RESOLVED; + } catch (MutexException e) { + Exceptions.printStackTrace(e); + } + return ProjectProblemsProvider.Result.create(status); + } + }); + } + return new Done(ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.UNRESOLVED)); + } + } private static final class Done implements Future { @@ -1292,6 +1405,173 @@ } } + private static final class ProfileProjectProviderImpl implements ProjectProblemsProvider, PropertyChangeListener, ChangeListener { + + private static final String RES_MANIFEST = "META-INF/MANIFEST.MF"; //NOI18N + private static final String ATTR_PROFILE = "Profile"; //NOI18N + + private final AntProjectHelper antProjectHelper; + private final ReferenceHelper referenceHelper; + private final PropertyEvaluator evaluator; + private final String profileProperty; + private final Set classPathProperties; + private final ProjectProblemsProviderSupport problemsProviderSupport; + private final AtomicBoolean listenersInitialized; + private volatile SourceLevelQuery.Result slRes; + + ProfileProjectProviderImpl( + @NonNull final AntProjectHelper antProjectHelper, + @NonNull final ReferenceHelper referenceHelper, + @NonNull final PropertyEvaluator evaluator, + @NonNull final String profileProperty, + @NonNull final String... classPathProperties) { + assert antProjectHelper != null; + assert referenceHelper != null; + assert evaluator != null; + assert profileProperty != null; + assert classPathProperties != null; + this.antProjectHelper = antProjectHelper; + this.referenceHelper = referenceHelper; + this.evaluator = evaluator; + this.profileProperty = profileProperty; + this.classPathProperties = new HashSet( + Arrays.asList(classPathProperties)); + this.problemsProviderSupport = new ProjectProblemsProviderSupport(this); + this.listenersInitialized = new AtomicBoolean(); + } + + @Override + public void addPropertyChangeListener(@NonNull final PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + problemsProviderSupport.addPropertyChangeListener(listener); + } + + @Override + public void removePropertyChangeListener(@NonNull final PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + problemsProviderSupport.removePropertyChangeListener(listener); + } + + @Override + @NbBundle.Messages({ + "LBL_InvalidProfile=Invalid Profile", + "DESC_InvalidProfile=The project profile ({0}) is lower than the profile of used libraries ({1}).", + "DESC_IllegalProfile=The project libraries have illegal value of profile." + }) + public Collection getProblems() { + listenen(); + return problemsProviderSupport.getProblems(new ProjectProblemsProviderSupport.ProblemsCollector() { + @Override + public Collection collectProblems() { + return ProjectManager.mutex().readAccess(new Mutex.Action>(){ + @Override + public Collection run() { + final String profile = slRes.getProfile(); + if (profile == null) { + return Collections.emptySet(); + } + Map> problems = collectJarsWithWrongProfile(profile); + if (problems.isEmpty()) { + return Collections.emptySet(); + } + final String minProfile = FixProfile.requiredProfile(problems); + return Collections.singleton( + ProjectProblem.createError( + LBL_InvalidProfile(), + minProfile != null ? + DESC_InvalidProfile(profile, minProfile) : + DESC_IllegalProfile(), + new ProfileResolver( + antProjectHelper, + profileProperty, + profile, + problems))); + } + }); + } + }); + } + + @Override + public void propertyChange(@NonNull final PropertyChangeEvent event) { + final String propertyName = event.getPropertyName(); + if (propertyName == null || classPathProperties.contains(propertyName)) { + problemsProviderSupport.fireProblemsChange(); + } + } + + @Override + public void stateChanged(ChangeEvent ce) { + problemsProviderSupport.fireProblemsChange(); + } + + private void listenen() { + if (listenersInitialized.compareAndSet(false, true)) { + evaluator.addPropertyChangeListener(this); + slRes = SourceLevelQuery.getSourceLevel2(antProjectHelper.getProjectDirectory()); + slRes.addChangeListener(this); + } + } + + @NonNull + private Map> collectJarsWithWrongProfile(@NonNull final String currentProfile) { + assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); + final Map> res = new HashMap>(); + final File base = FileUtil.toFile(antProjectHelper.getProjectDirectory()); + if (base != null) { + for (String cpId : classPathProperties) { + final ClassPath cp = ClassPathFactory.createClassPath( + ProjectClassPathSupport.createPropertyBasedClassPathImplementation( + base, + evaluator, + new String[] {cpId})); + for (FileObject root : cp.getRoots()) { + final FileObject manifestFile = root.getFileObject(RES_MANIFEST); + if (manifestFile != null) { + try { + final InputStream in = manifestFile.getInputStream(); + try { + Manifest manifest = new Manifest(in); + final Attributes attrs = manifest.getMainAttributes(); + final String profile = attrs.getValue(ATTR_PROFILE); + if (profile != null) { + final String normalizedProfile = FixProfile.normalize(profile); + if (normalizedProfile != null) { + if (currentProfile.compareTo(normalizedProfile) < 0) { + Map errInCp = res.get(cpId); + if (errInCp == null) { + errInCp = new HashMap(); + res.put(cpId, errInCp); + } + errInCp.put(root.toURI(), profile); + } + } else { + Map errInCp = res.get(cpId); + if (errInCp == null) { + errInCp = new HashMap(); + res.put(cpId, errInCp); + } + errInCp.put(root.toURI(), profile); + } + } + } finally { + in.close(); + } + } catch (IOException ioe) { + LOG.log( + Level.INFO, + "Cannot read Profile attribute from: {0}", //NOI18N + FileUtil.getFileDisplayName(manifestFile)); + } + } + } + + } + } + return res; + } + } + private static class OpenManagersWeakListener extends WeakReference implements Runnable, PropertyChangeListener { public OpenManagersWeakListener(final PropertyChangeListener listener) { diff --git a/java.project/src/org/netbeans/spi/java/project/support/ui/BrokenReferencesSupport.java b/java.project/src/org/netbeans/spi/java/project/support/ui/BrokenReferencesSupport.java --- a/java.project/src/org/netbeans/spi/java/project/support/ui/BrokenReferencesSupport.java +++ b/java.project/src/org/netbeans/spi/java/project/support/ui/BrokenReferencesSupport.java @@ -44,6 +44,7 @@ package org.netbeans.spi.java.project.support.ui; +import java.util.Arrays; import java.util.concurrent.Callable; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; @@ -232,6 +233,7 @@ * @see ProjectProblemsProvider * @since 1.48 */ + @NonNull public static ProjectProblemsProvider createReferenceProblemsProvider( @NonNull final AntProjectHelper projectHelper, @NonNull final ReferenceHelper referenceHelper, @@ -268,6 +270,7 @@ * @see ProjectProblemsProvider * @since 1.48 */ + @NonNull public static ProjectProblemsProvider createPlatformVersionProblemProvider( @NonNull final AntProjectHelper projectHelper, @NonNull final PropertyEvaluator evaluator, @@ -287,6 +290,46 @@ platformProperty, versionProperties); } + + /** + * Creates a {@link ProjectProblemsProvider} creating wrong JDK 8 Profile + * problems. + * @param projectHelper AntProjectHelper associated with the project + * @param referenceHelper ReferenceHelper associated with the project + * @param evaluator the {@link PropertyEvaluator} used to resolve broken references + * @param profileProperty the property holding the actual project profile + * @param classPathProperties an array of property names which values hold the + * classpaths to be checked. + * @return {@link ProjectProblemsProvider} to be placed into project lookup. + * + * @see ProjectProblemsProvider + * @since 1.53 + */ + @NonNull + public static ProjectProblemsProvider createProfileProblemProvider( + @NonNull final AntProjectHelper projectHelper, + @NonNull final ReferenceHelper referenceHelper, + @NonNull final PropertyEvaluator evaluator, + @NonNull final String profileProperty, + @NonNull final String... classPathProperties) { + Parameters.notNull("projectHelper", projectHelper); //NOI18N + Parameters.notNull("referenceHelper", referenceHelper); //NOI18N + Parameters.notNull("evaluator", evaluator); //NOI18N + Parameters.notNull("profileProperty", profileProperty); //NOI18N + Parameters.notNull("classPathProperties", classPathProperties); //NOI18N + final String[] safeClassPathProperties = Arrays.copyOf( + classPathProperties, + classPathProperties.length); + for (String safeClassPathProperty : safeClassPathProperties) { + Parameters.notNull("classPathProperties[]", safeClassPathProperty); //NOI18N + } + return ProjectProblemsProviders.createProfileProblemProvider( + projectHelper, + referenceHelper, + evaluator, + profileProperty, + safeClassPathProperties); + } /** * Service which may be {@linkplain ServiceProvider registered} to download remote libraries or otherwise define them.