blob: 52d4ea46f7aebc3a0caf50a64767459f34265693 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This is an implementation of an early-draft specification developed under the Java
* Community Process (JCP) and is made available for testing and evaluation purposes
* only. The code is not compatible with any specification of the JCP.
*
* Contributors:
* IBM Corporation - initial API and implementation
* Philippe Marschall <philippe.marschall@netcetera.ch> - [type wizards] Allow the creation of a compilation unit called package-info.java - https://bugs.eclipse.org/86168
* Michael Pellaton <michael.pellaton@netcetera.ch> - [type wizards] Allow the creation of a compilation unit called package-info.java - https://bugs.eclipse.org/86168
*******************************************************************************/
package org.eclipse.jdt.ui.wizards;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import org.eclipse.equinox.bidi.StructuredTextTypeHandlerFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.util.BidiUtils;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.PlatformUI;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.javadoc.JavaDocCommentReader;
import org.eclipse.jdt.internal.corext.util.InfoFilesUtil;
import org.eclipse.jdt.internal.corext.util.JavaConventionsUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.jdt.internal.ui.dialogs.TextFieldNavigationHandler;
import org.eclipse.jdt.internal.ui.javaeditor.DocumentAdapter;
import org.eclipse.jdt.internal.ui.text.javadoc.JavaDoc2HTMLTextReader;
import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.StringDialogField;
/**
* Wizard page to create a new package.
*
* <p>
* Note: This class is not intended to be subclassed, but clients can instantiate.
* To implement a different kind of a new package wizard page, extend <code>NewContainerWizardPage</code>.
* </p>
*
* @since 2.0
*
* @noextend This class is not intended to be subclassed by clients.
*/
public class NewPackageWizardPage extends NewContainerWizardPage {
private static final String PACKAGE_INFO_JAVA_FILENAME= JavaModelUtil.PACKAGE_INFO_JAVA;
private static final String PACKAGE_HTML_FILENAME= JavaModelUtil.PACKAGE_HTML;
private static final String PAGE_NAME= "NewPackageWizardPage"; //$NON-NLS-1$
private static final String PACKAGE= "NewPackageWizardPage.package"; //$NON-NLS-1$
private final static String SETTINGS_CREATEPACKAGE_INFO_JAVA= "create_package_info_java"; //$NON-NLS-1$
private StringDialogField fPackageDialogField;
private SelectionButtonDialogField fCreatePackageInfoJavaDialogField;
/*
* Status of last validation of the package field
*/
private IStatus fPackageStatus;
private IPackageFragment fCreatedPackageFragment;
/**
* Creates a new <code>NewPackageWizardPage</code>
*/
public NewPackageWizardPage() {
super(PAGE_NAME);
setTitle(NewWizardMessages.NewPackageWizardPage_title);
setDescription(NewWizardMessages.NewPackageWizardPage_description);
fCreatedPackageFragment= null;
PackageFieldAdapter adapter= new PackageFieldAdapter();
fPackageDialogField= new StringDialogField();
fPackageDialogField.setDialogFieldListener(adapter);
fPackageDialogField.setLabelText(NewWizardMessages.NewPackageWizardPage_package_label);
fCreatePackageInfoJavaDialogField= new SelectionButtonDialogField(SWT.CHECK);
fCreatePackageInfoJavaDialogField.setDialogFieldListener(adapter);
fCreatePackageInfoJavaDialogField.setLabelText(NewWizardMessages.NewPackageWizardPage_package_CreatePackageInfoJava);
fPackageStatus= new StatusInfo();
}
// -------- Initialization ---------
/**
* The wizard owning this page is responsible for calling this method with the
* current selection. The selection is used to initialize the fields of the wizard
* page.
*
* @param selection used to initialize the fields
*/
public void init(IStructuredSelection selection) {
IJavaElement jelem= getInitialJavaElement(selection);
initContainerPage(jelem);
String pName= ""; //$NON-NLS-1$
if (jelem != null) {
IPackageFragment pf= (IPackageFragment) jelem.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
if (pf != null && !pf.isDefaultPackage()) {
pName= pf.getElementName();
} else {
if (jelem.getJavaProject() != null) {
final IPackageFragmentRoot pkgFragmentRoot= getPackageFragmentRoot();
if (pkgFragmentRoot != null && pkgFragmentRoot.exists()) {
try {
IJavaElement[] packages= pkgFragmentRoot.getChildren();
if (packages.length == 1) { // only default package
String prName= jelem.getJavaProject().getElementName();
IStatus status= getPackageStatus(prName);
if (status.getSeverity() == IStatus.OK) {
pName= prName;
}
}
} catch (JavaModelException e) {
// fall through
}
}
}
}
}
setPackageText(pName, true);
IDialogSettings dialogSettings= getDialogSettings();
if (dialogSettings != null) {
IDialogSettings section= dialogSettings.getSection(PAGE_NAME);
if (section != null) {
boolean createPackageDocumentation= section.getBoolean(SETTINGS_CREATEPACKAGE_INFO_JAVA);
fCreatePackageInfoJavaDialogField.setSelection(createPackageDocumentation);
}
}
updateStatus(new IStatus[] { fContainerStatus, fPackageStatus });
}
// -------- UI Creation ---------
/*
* @see WizardPage#createControl
*/
@Override
public void createControl(Composite parent) {
initializeDialogUnits(parent);
Composite composite= new Composite(parent, SWT.NONE);
composite.setFont(parent.getFont());
int nColumns= 3;
GridLayout layout= new GridLayout();
layout.numColumns= 3;
composite.setLayout(layout);
Label label= new Label(composite, SWT.WRAP);
label.setText(NewWizardMessages.NewPackageWizardPage_info);
GridData gd= new GridData();
gd.widthHint= convertWidthInCharsToPixels(60);
gd.horizontalSpan= 3;
label.setLayoutData(gd);
createContainerControls(composite, nColumns);
createPackageControls(composite, nColumns);
setControl(composite);
Dialog.applyDialogFont(composite);
PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IJavaHelpContextIds.NEW_PACKAGE_WIZARD_PAGE);
}
/**
* @see org.eclipse.jface.dialogs.IDialogPage#setVisible(boolean)
*/
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
setFocus();
}
}
/**
* Sets the focus to the package name input field.
*/
protected void setFocus() {
fPackageDialogField.setFocus();
}
private void createPackageControls(Composite composite, int nColumns) {
fPackageDialogField.doFillIntoGrid(composite, nColumns - 1);
Text text= fPackageDialogField.getTextControl(null);
LayoutUtil.setWidthHint(text, getMaxFieldWidth());
LayoutUtil.setHorizontalGrabbing(text);
DialogField.createEmptySpace(composite);
TextFieldNavigationHandler.install(text);
fCreatePackageInfoJavaDialogField.doFillIntoGrid(composite, nColumns);
BidiUtils.applyBidiProcessing(fPackageDialogField.getTextControl(null), StructuredTextTypeHandlerFactory.JAVA);
}
// -------- PackageFieldAdapter --------
private class PackageFieldAdapter implements IDialogFieldListener {
// --------- IDialogFieldListener
@Override
public void dialogFieldChanged(DialogField field) {
fPackageStatus= getPackageStatus(getPackageText());
// tell all others
handleFieldChanged(PACKAGE);
}
}
// -------- update message ----------------
/*
* @see org.eclipse.jdt.ui.wizards.NewContainerWizardPage#handleFieldChanged(String)
*/
@Override
protected void handleFieldChanged(String fieldName) {
super.handleFieldChanged(fieldName);
if (fieldName == CONTAINER) {
fPackageStatus= getPackageStatus(getPackageText());
}
// do status line update
updateStatus(new IStatus[] { fContainerStatus, fPackageStatus });
}
// ----------- validation ----------
private IStatus validatePackageName(String text) {
IJavaProject project= getJavaProject();
if (project == null || !project.exists()) {
return JavaConventions.validatePackageName(text, JavaCore.VERSION_1_3, JavaCore.VERSION_1_3);
}
return JavaConventionsUtil.validatePackageName(text, project);
}
/**
* Validates the package name and returns the status of the validation.
*
* @param packName the package name
*
* @return the status of the validation
*/
private IStatus getPackageStatus(String packName) {
StatusInfo status= new StatusInfo();
if (packName.length() > 0) {
IStatus val= validatePackageName(packName);
if (val.getSeverity() == IStatus.ERROR) {
status.setError(Messages.format(NewWizardMessages.NewPackageWizardPage_error_InvalidPackageName, val.getMessage()));
return status;
} else if (val.getSeverity() == IStatus.WARNING) {
status.setWarning(Messages.format(NewWizardMessages.NewPackageWizardPage_warning_DiscouragedPackageName, val.getMessage()));
}
} else {
status.setError(NewWizardMessages.NewPackageWizardPage_error_EnterName);
return status;
}
IPackageFragmentRoot root= getPackageFragmentRoot();
if (root != null && root.getJavaProject().exists()) {
IPackageFragment pack= root.getPackageFragment(packName);
try {
IPath rootPath= root.getPath();
IPath outputPath= root.getJavaProject().getOutputLocation();
if (rootPath.isPrefixOf(outputPath) && !rootPath.equals(outputPath)) {
// if the bin folder is inside of our root, don't allow to name a package
// like the bin folder
IPath packagePath= pack.getPath();
if (outputPath.isPrefixOf(packagePath)) {
status.setError(NewWizardMessages.NewPackageWizardPage_error_IsOutputFolder);
return status;
}
}
if (pack.exists()) {
// it's ok for the package to exist as long we want to create package level documentation
// and the package level documentation doesn't exist
if (!isCreatePackageDocumentation() || packageDocumentationAlreadyExists(pack)) {
if (pack.containsJavaResources() || !pack.hasSubpackages()) {
status.setError(NewWizardMessages.NewPackageWizardPage_error_PackageExists);
} else {
status.setError(NewWizardMessages.NewPackageWizardPage_error_PackageNotShown);
}
}
} else {
IResource resource= pack.getResource();
if (resource != null && !ResourcesPlugin.getWorkspace().validateFiltered(resource).isOK()) {
status.setError(NewWizardMessages.NewPackageWizardPage_error_PackageNameFiltered);
return status;
}
URI location= pack.getResource().getLocationURI();
if (location != null) {
IFileStore store= EFS.getStore(location);
if (store.fetchInfo().exists()) {
status.setError(NewWizardMessages.NewPackageWizardPage_error_PackageExistsDifferentCase);
}
}
}
} catch (CoreException e) {
JavaPlugin.log(e);
}
}
return status;
}
private boolean packageDocumentationAlreadyExists(IPackageFragment pack) throws JavaModelException {
ICompilationUnit packageInfoJava= pack.getCompilationUnit(PACKAGE_INFO_JAVA_FILENAME);
if (packageInfoJava.exists()) {
return true;
}
Object[] nonJavaResources= pack.getNonJavaResources();
for (int i= 0; i < nonJavaResources.length; i++) {
Object resource= nonJavaResources[i];
if (resource instanceof IFile) {
IFile file= (IFile) resource;
String fileName= file.getName();
if (PACKAGE_HTML_FILENAME.equals(fileName)) {
return true;
}
}
}
return false;
}
/**
* Returns the content of the package input field.
*
* @return the content of the package input field
*/
public String getPackageText() {
return fPackageDialogField.getText();
}
/**
* Returns the content of the create package documentation input field.
*
* @return the content of the create package documentation input field
* @since 3.9
*/
public boolean isCreatePackageDocumentation() {
return fCreatePackageInfoJavaDialogField.isSelected();
}
/**
* Sets the content of the package input field to the given value.
*
* @param str the new package input field text
* @param canBeModified if <code>true</code> the package input
* field can be modified; otherwise it is read-only.
*/
public void setPackageText(String str, boolean canBeModified) {
fPackageDialogField.setText(str);
fPackageDialogField.setEnabled(canBeModified);
}
/**
* Returns the resource handle that corresponds to the element that was created or
* will be created.
* @return A resource or null if the page contains illegal values.
* @since 3.0
*/
public IResource getModifiedResource() {
IPackageFragmentRoot root= getPackageFragmentRoot();
if (root != null) {
IPackageFragment pack= root.getPackageFragment(getPackageText());
IResource packRes= pack.getResource();
if (isCreatePackageDocumentation()) {
if (JavaModelUtil.is50OrHigher(getJavaProject())) {
return pack.getCompilationUnit(PACKAGE_INFO_JAVA_FILENAME).getResource();
} else if (packRes instanceof IFolder){
return ((IFolder) packRes).getFile(PACKAGE_HTML_FILENAME);
}
}
return packRes;
}
return null;
}
// ---- creation ----------------
/**
* Returns a runnable that creates a package using the current settings.
*
* @return the runnable that creates the new package
*/
public IRunnableWithProgress getRunnable() {
return new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
createPackage(monitor);
} catch (CoreException e) {
throw new InvocationTargetException(e);
}
}
};
}
/**
* Returns the created package fragment. This method only returns a valid value
* after <code>getRunnable</code> or <code>createPackage</code> have been
* executed.
*
* @return the created package fragment
*/
public IPackageFragment getNewPackageFragment() {
return fCreatedPackageFragment;
}
/**
* Creates the new package using the entered field values.
*
* @param monitor a progress monitor to report progress. The progress
* monitor must not be <code>null</code>
* @throws CoreException Thrown if creating the package failed.
* @throws InterruptedException Thrown when the operation has been canceled.
* @since 2.1
*/
public void createPackage(IProgressMonitor monitor) throws CoreException, InterruptedException {
if (monitor == null) {
monitor= new NullProgressMonitor();
}
IPackageFragmentRoot root= getPackageFragmentRoot();
IPackageFragment pack= root.getPackageFragment(getPackageText());
if (pack.exists()) {
fCreatedPackageFragment= pack;
} else {
fCreatedPackageFragment= root.createPackageFragment(getPackageText(), true, monitor);
}
if (isCreatePackageDocumentation()) {
if (JavaModelUtil.is50OrHigher(getJavaProject())) {
createPackageInfoJava(monitor);
} else {
createPackageHtml(root, monitor);
}
}
// save whether package documentation should be created
IDialogSettings dialogSettings= getDialogSettings();
if (dialogSettings != null) {
IDialogSettings section= dialogSettings.getSection(PAGE_NAME);
if (section == null) {
section= dialogSettings.addNewSection(PAGE_NAME);
}
section.put(SETTINGS_CREATEPACKAGE_INFO_JAVA, isCreatePackageDocumentation());
}
if (monitor.isCanceled()) {
throw new InterruptedException();
}
}
private void createPackageInfoJava(IProgressMonitor monitor) throws CoreException {
StringBuilder fileContent= new StringBuilder();
fileContent.append("package "); //$NON-NLS-1$
fileContent.append(fCreatedPackageFragment.getElementName());
fileContent.append(";"); //$NON-NLS-1$
InfoFilesUtil.createInfoJavaFile(PACKAGE_INFO_JAVA_FILENAME, fileContent.toString(), fCreatedPackageFragment, monitor);
}
private void createPackageHtml(IPackageFragmentRoot root, IProgressMonitor monitor) throws CoreException {
IWorkspace workspace= ResourcesPlugin.getWorkspace();
IFolder createdPackage= workspace.getRoot().getFolder(fCreatedPackageFragment.getPath());
IFile packageHtml= createdPackage.getFile(PACKAGE_HTML_FILENAME);
String charset= packageHtml.getCharset();
String content= buildPackageHtmlContent(root, charset);
try {
packageHtml.create(new ByteArrayInputStream(content.getBytes(charset)), false, monitor);
} catch (UnsupportedEncodingException e) {
String message= "charset " + charset + " not supported by platform"; //$NON-NLS-1$ //$NON-NLS-2$
throw new CoreException(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, message, e));
}
}
private String buildPackageHtmlContent(IPackageFragmentRoot root, String charset) throws CoreException {
String lineDelimiter= StubUtility.getLineDelimiterUsed(root.getJavaProject());
StringBuilder content = new StringBuilder();
String fileComment= InfoFilesUtil.getFileComment(PACKAGE_INFO_JAVA_FILENAME, fCreatedPackageFragment, lineDelimiter);
String typeComment= InfoFilesUtil.getTypeComment(PACKAGE_INFO_JAVA_FILENAME, fCreatedPackageFragment, lineDelimiter);
content.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">"); //$NON-NLS-1$
content.append(lineDelimiter);
if (fileComment != null) {
content.append("<!--"); //$NON-NLS-1$
content.append(lineDelimiter);
content.append(stripJavaComments(fileComment));
content.append(lineDelimiter);
content.append("-->"); //$NON-NLS-1$
content.append(lineDelimiter);
}
content.append("<html>"); //$NON-NLS-1$
content.append(lineDelimiter);
content.append("<head>"); //$NON-NLS-1$
content.append(lineDelimiter);
content.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset="); //$NON-NLS-1$
content.append(charset);
content.append("\">"); //$NON-NLS-1$
content.append(lineDelimiter);
content.append("<title>"); //$NON-NLS-1$
content.append(fCreatedPackageFragment.getElementName());
content.append("</title>"); //$NON-NLS-1$
content.append(lineDelimiter);
content.append("</head>"); //$NON-NLS-1$
content.append(lineDelimiter);
content.append("<body>"); //$NON-NLS-1$
content.append(lineDelimiter);
if (typeComment != null) {
content.append(stripJavaComments(typeComment));
content.append(lineDelimiter);
}
content.append("</body>"); //$NON-NLS-1$
content.append(lineDelimiter);
content.append("</html>"); //$NON-NLS-1$
return content.toString();
}
private String stripJavaComments(String comment) {
DocumentAdapter documentAdapter= new DocumentAdapter(null, fCreatedPackageFragment.getPath());
try {
documentAdapter.setContents(comment);
return getString(new JavaDoc2HTMLTextReader(new JavaDocCommentReader(documentAdapter, 0, comment.length())));
} finally {
documentAdapter.close();
}
}
private static String getString(Reader reader) {
StringBuffer buf= new StringBuffer();
char[] buffer= new char[1024];
int count;
try {
while ((count= reader.read(buffer)) != -1)
buf.append(buffer, 0, count);
} catch (IOException e) {
return null;
}
return buf.toString();
}
}