blob: 27e98604d405810ea3a08b1d35fcb5b694df3bc5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.ui.correction.java;
import java.text.MessageFormat;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.ui.text.java.ClasspathFixProcessor.ClasspathFixProposal;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.ltk.core.refactoring.*;
import org.eclipse.osgi.service.resolver.ExportPackageDescription;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.IBaseModel;
import org.eclipse.pde.core.plugin.IPluginImport;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.bundle.BundlePluginBase;
import org.eclipse.pde.internal.core.ibundle.*;
import org.eclipse.pde.internal.core.project.PDEProject;
import org.eclipse.pde.internal.core.text.bundle.*;
import org.eclipse.pde.internal.ui.*;
import org.eclipse.pde.internal.ui.util.ModelModification;
import org.eclipse.pde.internal.ui.util.PDEModelUtility;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.text.edits.TextEdit;
import org.osgi.framework.Constants;
/**
* A factory class used to create resolutions for JDT problem markers which involve modifying a project's MANIFEST.MF (or possibly plugin.xml)
* @since 3.4
*/
public class JavaResolutionFactory {
/**
* Type constant for a proposal of type IJavaCompletionProposal
*/
public static final int TYPE_JAVA_COMPLETION = 0x01;
/**
* Type constant for a proposal of type ClasspathFixProposal
*/
public static final int TYPE_CLASSPATH_FIX = 0x02;
/**
* This class represents a Change which will be applied to a Manifest file. This change is meant to be
* used to create an IJavaCompletionProposal or ClasspathFixProposal.
*/
private static abstract class AbstractManifestChange extends Change {
private Object fChangeObject;
private IProject fProject;
private CompilationUnit fCompilationUnit;
private String fQualifiedTypeToImport;
private AbstractManifestChange(IProject project, Object obj) {
fProject = project;
fChangeObject = obj;
}
public AbstractManifestChange(IProject project, Object changeObj, CompilationUnit cu,
String qualifiedTypeToImport) {
this(project, changeObj);
fCompilationUnit = cu;
fQualifiedTypeToImport = qualifiedTypeToImport;
}
protected Object getChangeObject() {
return fChangeObject;
}
protected IProject getProject() {
return fProject;
}
protected CompilationUnit getCompilationUnit() {
return fCompilationUnit;
}
protected String getQualifiedTypeToImport() {
return fQualifiedTypeToImport;
}
/*
* Provides an image for the Change
*/
public abstract Image getImage();
/*
* Provides a description for the Change
*/
public abstract String getDescription();
/*
* Added to allow creation of an "undo" change for each AbstractManifestChange
*/
protected boolean isUndo() {
return false;
}
protected void insertImport(CompilationUnit compilationUnit, String qualifiedTypeToImport, IProgressMonitor pm)
throws CoreException {
if (compilationUnit == null || qualifiedTypeToImport == null) {
return;
}
ImportRewrite rewrite = ImportRewrite.create(compilationUnit, true);
if (rewrite == null) {
return;
}
if (!isUndo()) {
rewrite.addImport(qualifiedTypeToImport);
} else {
rewrite.removeImport(qualifiedTypeToImport);
}
TextEdit rewriteImports = rewrite.rewriteImports(pm);
ICompilationUnit iCompilationUnit = (ICompilationUnit) compilationUnit.getJavaElement()
.getAdapter(IOpenable.class);
performTextEdit(rewriteImports, (IFile) iCompilationUnit.getResource(), pm);
}
private void performTextEdit(TextEdit textEdit, IFile file, IProgressMonitor pm)
throws CoreException {
TextFileChange textFileChange = new TextFileChange("Add import for " + fQualifiedTypeToImport, file); //$NON-NLS-1$
textFileChange.setSaveMode(TextFileChange.KEEP_SAVE_STATE);
textFileChange.setEdit(textEdit);
textFileChange.perform(pm);
}
@Override
public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException {
return RefactoringStatus.create(Status.OK_STATUS);
}
@Override
public Object getModifiedElement() {
return getProject();
}
@Override
public void initializeValidationData(IProgressMonitor pm) {
}
}
/*
* A Change which will add a Require-Bundle entry to resolve the given
* dependency or add multiple Require-Bundle entries to resolve the dependency
* based on description name
*/
private static class RequireBundleManifestChange extends AbstractManifestChange {
private RequireBundleManifestChange(IProject project, ExportPackageDescription desc, CompilationUnit cu,
String qualifiedTypeToImport) {
super(project, desc, cu, qualifiedTypeToImport);
}
private RequireBundleManifestChange(IProject project, String desc, CompilationUnit cu,
String qualifiedTypeToImport) {
super(project, desc, cu, qualifiedTypeToImport);
}
@Override
public Change perform(IProgressMonitor pm) throws CoreException {
PDEModelUtility.modifyModel(new ModelModification(getProject()) {
@Override
protected void modifyModel(IBaseModel model, IProgressMonitor monitor) throws CoreException {
if (!(model instanceof IPluginModelBase))
return;
IPluginModelBase base = (IPluginModelBase) model;
String[] pluginIdStrings = null;
if ("JUnit 5 bundles".equals(getChangeObject())) { //$NON-NLS-1$
pluginIdStrings = getJUnit5Bundles();
}
if (getChangeObject() instanceof ExportPackageDescription) {
pluginIdStrings = new String[1];
pluginIdStrings[0] = ((ExportPackageDescription) getChangeObject()).getSupplier()
.getSymbolicName();
}
for (int i = 0; i < pluginIdStrings.length; i++) {
String pluginId = pluginIdStrings[i];
if (!isUndo()) {
IPluginImport impt = base.getPluginFactory().createImport();
impt.setId(pluginId);
base.getPluginBase().add(impt);
} else {
IPluginImport[] imports = base.getPluginBase().getImports();
for (IPluginImport pluginImport : imports) {
if (pluginImport.getId().equals(pluginId)) {
base.getPluginBase().remove(pluginImport);
}
}
}
}
}
private String[] getJUnit5Bundles() {
return new String[] { "org.junit", "org.junit.jupiter.api" }; //$NON-NLS-1$ //$NON-NLS-2$
}
}, new NullProgressMonitor());
insertImport(getCompilationUnit(), getQualifiedTypeToImport(), pm);
if (!isUndo()) {
if (getChangeObject() instanceof ExportPackageDescription) {
return new RequireBundleManifestChange(getProject(), (ExportPackageDescription) getChangeObject(),
getCompilationUnit(), getQualifiedTypeToImport()) {
@Override
public boolean isUndo() {
return true;
}
};
}
if (getChangeObject() instanceof String) {
return new RequireBundleManifestChange(getProject(),(String) getChangeObject(),
getCompilationUnit(), getQualifiedTypeToImport()) {
@Override
public boolean isUndo() {
return true;
}
};
}
}
return null;
}
@Override
public Image getImage() {
return PDEPlugin.getDefault().getLabelProvider().get(PDEPluginImages.DESC_REQ_PLUGIN_OBJ);
}
@Override
public String getDescription() {
return PDEUIMessages.UnresolvedImportFixProcessor_2;
}
@Override
public String getName() {
if (!isUndo()) {
if(getChangeObject() instanceof String) {
return MessageFormat.format(PDEUIMessages.UnresolvedImportFixProcessor_0,
(getChangeObject().toString()));
}
return MessageFormat.format(PDEUIMessages.UnresolvedImportFixProcessor_0, ((ExportPackageDescription) getChangeObject()).getExporter().getName());
}
return MessageFormat.format(PDEUIMessages.UnresolvedImportFixProcessor_1, ((ExportPackageDescription) getChangeObject()).getExporter().getName());
}
@Override
public Object getModifiedElement() {
IFile[] files = new IFile[] {PDEProject.getManifest(getProject()), PDEProject.getPluginXml(getProject())};
for (IFile file : files) {
if (file.exists())
return file;
}
return super.getModifiedElement();
}
}
/*
* A Change which will add an Import-Package entry to resolve the given dependency
*/
private static class ImportPackageManifestChange extends AbstractManifestChange {
private ImportPackageManifestChange(IProject project, ExportPackageDescription desc) {
super(project, desc);
}
private ImportPackageManifestChange(IProject project, ExportPackageDescription desc, CompilationUnit cu,
String qualifiedTypeToImport) {
super(project, desc, cu, qualifiedTypeToImport);
}
@Override
public Change perform(IProgressMonitor pm) throws CoreException {
PDEModelUtility.modifyModel(new ModelModification(getProject()) {
@Override
protected void modifyModel(IBaseModel model, IProgressMonitor monitor) throws CoreException {
if (!(model instanceof IBundlePluginModelBase))
return;
IBundlePluginModelBase base = (IBundlePluginModelBase) model;
IBundle bundle = base.getBundleModel().getBundle();
String pkgId = ((ExportPackageDescription) getChangeObject()).getName();
IManifestHeader header = bundle.getManifestHeader(Constants.IMPORT_PACKAGE);
if (header == null) {
bundle.setHeader(Constants.IMPORT_PACKAGE, pkgId);
} else if (header instanceof ImportPackageHeader) {
ImportPackageHeader ipHeader = (ImportPackageHeader) header;
int manifestVersion = BundlePluginBase.getBundleManifestVersion(bundle);
String versionAttr = (manifestVersion < 2) ? ICoreConstants.PACKAGE_SPECIFICATION_VERSION : Constants.VERSION_ATTRIBUTE;
ImportPackageObject impObject = new ImportPackageObject((ManifestHeader) header, (ExportPackageDescription) getChangeObject(), versionAttr);
if (!isUndo()) {
ipHeader.addPackage(impObject);
} else {
ipHeader.removePackage(impObject);
}
}
}
}, new NullProgressMonitor());
insertImport(getCompilationUnit(), getQualifiedTypeToImport(), pm);
if (!isUndo())
return new ImportPackageManifestChange(getProject(), (ExportPackageDescription) getChangeObject()) {
@Override
public boolean isUndo() {
return true;
}
};
return null;
}
@Override
public String getDescription() {
return PDEUIMessages.UnresolvedImportFixProcessor_5;
}
@Override
public Image getImage() {
return PDEPlugin.getDefault().getLabelProvider().get(PDEPluginImages.DESC_BUNDLE_OBJ);
}
@Override
public String getName() {
if (!isUndo()) {
return MessageFormat.format(PDEUIMessages.UnresolvedImportFixProcessor_3, ((ExportPackageDescription) getChangeObject()).getName());
}
return MessageFormat.format(PDEUIMessages.UnresolvedImportFixProcessor_4, ((ExportPackageDescription) getChangeObject()).getName());
}
@Override
public Object getModifiedElement() {
IFile file = PDEProject.getManifest(getProject());
if (file.exists())
return file;
return super.getModifiedElement();
}
}
private static class ExportPackageChange extends AbstractManifestChange {
public ExportPackageChange(IProject project, IPackageFragment fragment) {
super(project, fragment);
}
@Override
public Change perform(IProgressMonitor pm) throws CoreException {
ModelModification mod = new ModelModification(getProject()) {
@Override
protected void modifyModel(IBaseModel model, IProgressMonitor monitor) throws CoreException {
if (model instanceof IBundlePluginModelBase) {
IBundle bundle = ((IBundlePluginModelBase) model).getBundleModel().getBundle();
ExportPackageHeader header = (ExportPackageHeader) bundle.getManifestHeader(Constants.EXPORT_PACKAGE);
if (header == null) {
bundle.setHeader(Constants.EXPORT_PACKAGE, ""); //$NON-NLS-1$
header = (ExportPackageHeader) bundle.getManifestHeader(Constants.EXPORT_PACKAGE);
}
header.addPackage(new ExportPackageObject(header, (IPackageFragment) getChangeObject(), Constants.VERSION_ATTRIBUTE));
}
}
};
PDEModelUtility.modifyModel(mod, new NullProgressMonitor());
// No plans to use as ClasspathFixProposal, therefore we don't have to worry about an undo
return null;
}
@Override
public String getName() {
return NLS.bind(PDEUIMessages.ForbiddenAccessProposal_quickfixMessage, new String[] {((IPackageFragment) getChangeObject()).getElementName(), getProject().getName()});
}
@Override
public Image getImage() {
return PDEPluginImages.get(PDEPluginImages.OBJ_DESC_BUNDLE);
}
@Override
public Object getModifiedElement() {
IFile file = PDEProject.getManifest(getProject());
if (file.exists())
return file;
return super.getModifiedElement();
}
@Override
public String getDescription() {
// No plans to use as ClasspathFixProposal, therefore we don't have to implement a description
return null;
}
}
/**
* Creates and returns a proposal which create a Require-Bundle entry in the
* MANIFEST.MF (or corresponding plugin.xml) for the supplier of desc. The
* object will be of the type specified by the type argument.
*
* @param project
* the project to be updated
* @param desc
* an ExportPackageDescription from the bundle that is to be
* added as a Require-Bundle dependency
* @param type
* the type of the proposal to be returned
* @param relevance
* the relevance of the new proposal
* @param qualifiedTypeToImport
* the qualified type name of the type that requires this
* proposal. If this argument and cu are supplied the proposal
* will add an import statement for this type to the source file
* in which the proposal was invoked.
* @param cu
* the AST root of the java source file in which this fix was
* invoked
* @see JavaResolutionFactory#TYPE_JAVA_COMPLETION
* @see JavaResolutionFactory#TYPE_CLASSPATH_FIX
*/
public static final Object createRequireBundleProposal(IProject project, ExportPackageDescription desc, int type,
int relevance, CompilationUnit cu, String qualifiedTypeToImport) {
if (desc.getSupplier() == null)
return null;
AbstractManifestChange change = new RequireBundleManifestChange(project, desc, cu, qualifiedTypeToImport);
return createWrapper(change, type, relevance);
}
/**
* Creates and returns a proposal which create multiple Require-Bundle entry in the
* MANIFEST.MF (or corresponding plugin.xml) for desc name.
*
* @param project
* the project to be updated
* @param desc
* multiple bundles that is to be added as a Require-Bundle dependency
* based on description name
* @param type
* the type of the proposal to be returned
* @param relevance
* the relevance of the new proposal
* @param qualifiedTypeToImport
* the qualified type name of the type that requires this
* proposal. If this argument and cu are supplied the proposal
* will add an import statement for this type to the source file
* in which the proposal was invoked.
* @param cu
* the AST root of the java source file in which this fix was
* invoked
* @see JavaResolutionFactory#TYPE_JAVA_COMPLETION
* @see JavaResolutionFactory#TYPE_CLASSPATH_FIX
*/
public static final Object createRequireBundleProposal(IProject project, String desc, int type, int relevance,
CompilationUnit cu, String qualifiedTypeToImport) {
if (desc == null)
return null;
AbstractManifestChange change = new RequireBundleManifestChange(project, desc, cu, qualifiedTypeToImport);
return createWrapper(change, type, relevance);
}
/**
* Creates and returns a proposal which create an Import-Package entry in
* the MANIFEST.MF for the package represented by desc. The object will be
* of the type specified by the type argument.
*
* @param project
* the project to be updated
* @param desc
* an ExportPackageDescription which represents the package to be
* added
* @param type
* the type of the proposal to be returned
* @param relevance
* the relevance of the new proposal
* @param qualifiedTypeToImport
* the qualified type name of the type that requires this
* proposal. If this argument and cu are supplied the proposal
* will add an import statement for this type to the source file
* in which the proposal was invoked.
* @param cu
* the AST root of the java source file in which this fix was
* invoked
* @see JavaResolutionFactory#TYPE_JAVA_COMPLETION
* @see JavaResolutionFactory#TYPE_CLASSPATH_FIX
*/
public static final Object createImportPackageProposal(IProject project, ExportPackageDescription desc, int type,
int relevance, CompilationUnit cu, String qualifiedTypeToImport) {
AbstractManifestChange change = new ImportPackageManifestChange(project, desc, cu, qualifiedTypeToImport);
return createWrapper(change, type, relevance);
}
public static final IJavaCompletionProposal createSearchRepositoriesProposal(String packageName) {
return new SearchRepositoriesForIUProposal(packageName);
}
/**
* Creates and returns a proposal which create an Export-Package entry in the MANIFEST.MF for the package represented by
* pkg. The object will be of the type specified by the type argument.
* @param project the project to be updated
* @param pkg an IPackageFragment which represents the package to be added
* @param type the type of the proposal to be returned
* @param relevance the relevance of the new proposal
* @see JavaResolutionFactory#TYPE_JAVA_COMPLETION
* @see JavaResolutionFactory#TYPE_CLASSPATH_FIX
*/
public static final Object createExportPackageProposal(IProject project, IPackageFragment pkg, int type, int relevance) {
AbstractManifestChange change = new ExportPackageChange(project, pkg);
return createWrapper(change, type, relevance);
}
private static final Object createWrapper(AbstractManifestChange change, int type, int relevance) {
switch (type) {
case TYPE_JAVA_COMPLETION :
return createJavaCompletionProposal(change, relevance);
case TYPE_CLASSPATH_FIX :
return createClasspathFixProposal(change, relevance);
}
return null;
}
// Methods to wrap a AbstractMethodChange into a consumable format
/**
* Creates and returns a ClasspathFixProposal for the given AbstractManifestChange
* @param change the modification which should be performed by the proposal
* @since 3.4
* @see AbstractManifestChange
*/
public final static ClasspathFixProposal createClasspathFixProposal(final AbstractManifestChange change, final int relevance) {
return new ClasspathFixProposal() {
@Override
public Change createChange(IProgressMonitor monitor) throws CoreException {
return change;
}
@Override
public String getAdditionalProposalInfo() {
return change.getDescription();
}
@Override
public String getDisplayString() {
return change.getName();
}
@Override
public Image getImage() {
return change.getImage();
}
@Override
public int getRelevance() {
return relevance;
}
};
}
/**
* Creates and returns an IJavaCompletionProposal for the given AbstractManifestChange with the given relevance.
* @param change the modification which should be performed by the proposal
* @param relevance the relevance of the IJavaCompletionProposal
* @since 3.4
* @see AbstractManifestChange
*/
public final static IJavaCompletionProposal createJavaCompletionProposal(final AbstractManifestChange change, final int relevance) {
return new IJavaCompletionProposal() {
@Override
public int getRelevance() {
return relevance;
}
@Override
public void apply(IDocument document) {
try {
change.perform(new NullProgressMonitor());
} catch (CoreException e) {
}
}
@Override
public String getAdditionalProposalInfo() {
return change.getDescription();
}
@Override
public IContextInformation getContextInformation() {
return null;
}
@Override
public String getDisplayString() {
return change.getName();
}
@Override
public Image getImage() {
return change.getImage();
}
@Override
public Point getSelection(IDocument document) {
return null;
}
};
}
}