| /******************************************************************************* |
| * Copyright (c) 2004, 2018 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 |
| * Harald Albers <eclipse@albersweb.de> - [type wizards] New Annotation dialog could allow generating @Documented, @Retention and @Target - https://bugs.eclipse.org/339292 |
| *******************************************************************************/ |
| package org.eclipse.jdt.ui.wizards; |
| |
| import java.lang.annotation.Documented; |
| import java.lang.annotation.ElementType; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.annotation.Target; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| |
| import org.eclipse.ui.PlatformUI; |
| |
| import org.eclipse.jdt.core.IBuffer; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaModelException; |
| |
| import org.eclipse.jdt.internal.core.manipulation.StubUtility; |
| import org.eclipse.jdt.internal.corext.util.JavaModelUtil; |
| |
| import org.eclipse.jdt.internal.ui.IJavaHelpContextIds; |
| 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.SelectionButtonDialogField; |
| import org.eclipse.jdt.internal.ui.wizards.dialogfields.SelectionButtonDialogFieldGroup; |
| |
| /** |
| * Wizard page to create a new annotation type. |
| * <p> |
| * Note: This class is not intended to be subclassed, but clients can instantiate. |
| * To implement a different kind of a new annotation wizard page, extend <code>NewTypeWizardPage</code>. |
| * </p> |
| * |
| * @since 3.1 |
| * |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class NewAnnotationWizardPage extends NewTypeWizardPage { |
| |
| private final static String PAGE_NAME= "NewAnnotationWizardPage"; //$NON-NLS-1$ |
| private final static int TYPE = NewTypeWizardPage.ANNOTATION_TYPE; |
| |
| private final static String SETTINGS_ADD_DOCUMENTED= "add_documented"; //$NON-NLS-1$ |
| |
| private AddRetentionControl fRetentionSelection; |
| |
| private AddTargetControl fTargetSelection; |
| |
| private SelectionButtonDialogField fDocumentedSelection; |
| |
| /** |
| * Create a new <code>NewAnnotationWizardPage</code> |
| */ |
| public NewAnnotationWizardPage() { |
| super(TYPE, PAGE_NAME); |
| |
| setTitle(NewWizardMessages.NewAnnotationWizardPage_title); |
| setDescription(NewWizardMessages.NewAnnotationWizardPage_description); |
| |
| fRetentionSelection= new AddRetentionControl(); |
| fTargetSelection= new AddTargetControl(); |
| fDocumentedSelection= new SelectionButtonDialogField(SWT.CHECK); |
| fDocumentedSelection.setLabelText(NewWizardMessages.NewAnnotationWizardPage_add_documented); |
| } |
| |
| // -------- 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); |
| initTypePage(jelem); |
| initAnnotationPage(); |
| doStatusUpdate(); |
| } |
| |
| // ------ validation -------- |
| |
| private void initAnnotationPage() { |
| IDialogSettings dialogSettings= getDialogSettings(); |
| IDialogSettings section= null; |
| if (dialogSettings != null) { |
| section= dialogSettings.getSection(PAGE_NAME); |
| } |
| restoreSettings(section); |
| } |
| |
| private void restoreSettings(IDialogSettings section) { |
| if (section != null) { |
| boolean addDocumented= section.getBoolean(SETTINGS_ADD_DOCUMENTED); |
| fDocumentedSelection.setSelection(addDocumented); |
| } |
| |
| fRetentionSelection.init(section); |
| fTargetSelection.init(section); |
| } |
| |
| @Override |
| protected IStatus containerChanged() { |
| IStatus status= super.containerChanged(); |
| if (status.isOK()) { |
| IJavaProject javaProject= getJavaProject(); |
| fRetentionSelection.setProject(javaProject); |
| fTargetSelection.setProject(javaProject); |
| } |
| return status; |
| } |
| |
| private void doStatusUpdate() { |
| // all used component status |
| IStatus[] status= new IStatus[] { |
| fContainerStatus, |
| isEnclosingTypeSelected() ? fEnclosingTypeStatus : fPackageStatus, |
| fTypeNameStatus, |
| fModifierStatus, |
| }; |
| |
| // the mode severe status will be displayed and the OK button enabled/disabled. |
| updateStatus(status); |
| } |
| |
| |
| /* |
| * @see NewContainerWizardPage#handleFieldChanged |
| */ |
| @Override |
| protected void handleFieldChanged(String fieldName) { |
| super.handleFieldChanged(fieldName); |
| |
| doStatusUpdate(); |
| } |
| |
| |
| // ------ UI -------- |
| |
| /** |
| * Control for adding an annotation with enum-based values to the generated source code. |
| * |
| * @param <A> the class to add as an annotation |
| * @param <E> the enum class that supplies values for the annotation |
| */ |
| private abstract static class AddAnnotationControl<A, E extends Enum<E>> { |
| private static final String SETTINGS_ENABLED= "enabled"; //$NON-NLS-1$ |
| private static final String SETTINGS_SELECTED_ENUMS= "selectedEnums"; //$NON-NLS-1$ |
| |
| /** |
| * A checkbox for enabling the addition of this annotation. |
| */ |
| protected final SelectionButtonDialogField fEnableButton; |
| |
| /** |
| * A group of buttons for choosing between the available enum values for the annotation. |
| */ |
| protected final SelectionButtonDialogFieldGroup fEnumButtons; |
| |
| /** |
| * The annotation whose addition this control represents. |
| */ |
| private final Class<A> fAnnotationClass; |
| |
| /** |
| * The enum from which one or more constants should be added as values to the annotation. |
| */ |
| private final Class<E> fEnumClass; |
| |
| private boolean fControlsAreCreated; |
| |
| /** |
| * The Java project in which the annotation is going to be created. The availability of enum |
| * constants may depend on the project, e.g. its source level. |
| */ |
| protected IJavaProject fJavaProject; |
| |
| public AddAnnotationControl(int style, String enableLabel, Class<A> annotationClass, Class<E> enumClass, int nColumns) { |
| fAnnotationClass= annotationClass; |
| fEnumClass= enumClass; |
| |
| String[] enumLabels= toStringArray(enumClass); |
| |
| fEnableButton= new SelectionButtonDialogField(SWT.CHECK); |
| fEnableButton.setLabelText(enableLabel); |
| |
| fEnumButtons= new SelectionButtonDialogFieldGroup(style, enumLabels, nColumns); |
| |
| fEnableButton.setDialogFieldListener(field -> fEnumButtons.setEnabled(fEnableButton.isSelected())); |
| } |
| |
| private String[] toStringArray(Class<E> enumClass) { |
| E[] enums= enumClass.getEnumConstants(); |
| String[] strings= new String[enums.length]; |
| for (Enum<?> en : enums) { |
| strings[en.ordinal()]= labelFor(en); |
| } |
| return strings; |
| } |
| |
| protected String labelFor(Enum<?> en) { |
| String name= en.name(); |
| String first= name.substring(0, 1).toUpperCase(); |
| String rest= name.substring(1).toLowerCase().replace('_', ' '); |
| return first + rest; |
| } |
| |
| public void init(IDialogSettings settings) { |
| boolean enabled= false; |
| String[] selectedEnums= defaultSelectedEnums(); |
| |
| if (settings != null) { |
| IDialogSettings section= settings.getSection(dialogSettingsSectionName()); |
| if (section != null) { |
| enabled= section.getBoolean(SETTINGS_ENABLED); |
| selectedEnums= section.getArray(SETTINGS_SELECTED_ENUMS); |
| } |
| } |
| |
| applyInitialSettings(enabled, selectedEnums); |
| } |
| |
| protected String[] defaultSelectedEnums() { |
| return new String[] {}; |
| } |
| |
| private void applyInitialSettings(boolean enabled, String[] selectedEnumsAsStrings) { |
| fEnableButton.setSelection(enabled); |
| fEnumButtons.setEnabled(enabled); |
| |
| fEnumButtons.setSelection(0, false); |
| for (String string : selectedEnumsAsStrings) { |
| E en= Enum.valueOf(fEnumClass, string); |
| fEnumButtons.setSelection(en.ordinal(), true); |
| } |
| } |
| |
| /** |
| * @return the section name under which this control persists its settings. |
| */ |
| abstract protected String dialogSettingsSectionName(); |
| |
| public void persistSettings(IDialogSettings settings) { |
| String sectionName= dialogSettingsSectionName(); |
| IDialogSettings section= settings.getSection(sectionName); |
| if (section == null) { |
| section= settings.addNewSection(sectionName); |
| } |
| section.put(SETTINGS_ENABLED, isEnabled()); |
| section.put(SETTINGS_SELECTED_ENUMS, selectedEnumsAsStrings()); |
| } |
| |
| private String[] selectedEnumsAsStrings() { |
| List<String> resultList= new ArrayList<>(); |
| |
| for (E en : allEnums()) { |
| if (isSelected(en)) { |
| resultList.add(en.name()); |
| } |
| } |
| |
| String[] resultArray= resultList.toArray(new String[] {}); |
| return resultArray; |
| } |
| |
| private E[] allEnums() { |
| return fEnumClass.getEnumConstants(); |
| } |
| |
| public void doFillIntoGrid(Composite parent, int nColumns) { |
| Button button= fEnableButton.getSelectionButton(parent); |
| GridData gdButton= new GridData(GridData.VERTICAL_ALIGN_BEGINNING); |
| button.setLayoutData(gdButton); |
| |
| Composite buttonsGroup= fEnumButtons.getSelectionButtonsGroup(parent); |
| GridData gdButtonsGroup= new GridData(); |
| gdButtonsGroup.horizontalSpan= nColumns - 1; |
| buttonsGroup.setLayoutData(gdButtonsGroup); |
| |
| fControlsAreCreated= true; |
| updateButtons(); |
| } |
| |
| /** |
| * Sets or changes the target <code>IJavaProject</code>. The availability of enum constants |
| * may depend on the project, e.g. its source level. |
| * |
| * @param javaProject the new Java project in which the annotation will be created. |
| */ |
| public void setProject(IJavaProject javaProject) { |
| fJavaProject= javaProject; |
| updateButtons(); |
| } |
| |
| private void updateButtons() { |
| if (fControlsAreCreated && fJavaProject != null) { |
| updateAvailableButtons(); |
| } |
| } |
| |
| protected void updateAvailableButtons() { |
| // after changing the project, the availability of buttons might change. |
| } |
| |
| /** |
| * @return <code>true</code> if the control is enabled, else <code>false</code> |
| */ |
| public boolean isEnabled() { |
| return fEnableButton.isSelected(); |
| } |
| |
| public void addAnnotation(IType newType, ImportsManager imports, String lineDelimiter) throws JavaModelException { |
| if (isEnabled()) { |
| List<E> selectedEnums= availableSelectedEnums(); |
| if (selectedEnums.size() > 0) { |
| String annotation= createAnnotationAndImports(selectedEnums, imports, lineDelimiter); |
| |
| int start= newType.getSourceRange().getOffset(); |
| IBuffer buffer= newType.getCompilationUnit().getBuffer(); |
| buffer.replace(start, 0, annotation); |
| } |
| } |
| } |
| |
| private List<E> availableSelectedEnums() { |
| List<E> resultList= new ArrayList<>(); |
| for (E en : allEnums()) { |
| if (isEnabled(en) && isSelected(en)) { |
| resultList.add(en); |
| } |
| } |
| return resultList; |
| } |
| |
| private boolean isEnabled(E en) { |
| return fEnumButtons.isEnabled(en.ordinal()); |
| } |
| |
| private boolean isSelected(E en) { |
| return fEnumButtons.isSelected(en.ordinal()); |
| } |
| |
| private String createAnnotationAndImports(List<E> selectedEnums, ImportsManager imports, String lineDelimiter) { |
| StringBuilder buffer= new StringBuilder(); |
| |
| String annotationTypeName= imports.addImport(fAnnotationClass.getName()); |
| buffer.append("@"); //$NON-NLS-1$ |
| buffer.append(annotationTypeName); |
| buffer.append("("); //$NON-NLS-1$ |
| |
| if (selectedEnums.size() > 1) { |
| buffer.append("{"); //$NON-NLS-1$ |
| } |
| |
| for (Enum<?> en : selectedEnums) { |
| String enumTypeName= imports.addStaticImport(en.getClass().getName(), en.name(), true); |
| buffer.append(enumTypeName); |
| buffer.append(", "); //$NON-NLS-1$ |
| } |
| |
| buffer.delete(buffer.length() - 2, buffer.length()); |
| |
| if (selectedEnums.size() > 1) { |
| buffer.append("}"); //$NON-NLS-1$ |
| } |
| |
| buffer.append(")"); //$NON-NLS-1$ |
| buffer.append(lineDelimiter); |
| |
| return buffer.toString(); |
| } |
| } |
| |
| /** |
| * Control for adding <code>@Retention(RetentionPolicy)</code> |
| */ |
| private static class AddRetentionControl extends AddAnnotationControl<Retention, RetentionPolicy> { |
| private static final String SETTINGS_SECTION_NAME= "AddRetention"; //$NON-NLS-1$ |
| |
| private static final String[] MNEMONICS= { "S", "l", "n" }; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ |
| |
| public AddRetentionControl() { |
| super(SWT.RADIO, NewWizardMessages.NewAnnotationWizardPage_add_retention, Retention.class, RetentionPolicy.class, 3); |
| } |
| |
| @Override |
| protected String labelFor(Enum<?> en) { |
| String label= super.labelFor(en); |
| String mnemonic= MNEMONICS[en.ordinal()]; |
| return label.replaceFirst(mnemonic, "&" + mnemonic); //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected String dialogSettingsSectionName() { |
| return SETTINGS_SECTION_NAME; |
| } |
| |
| @Override |
| protected String[] defaultSelectedEnums() { |
| return new String[] { RetentionPolicy.CLASS.name() }; |
| } |
| } |
| |
| /** |
| * Control for adding <code>@Target([ElementType])</code> |
| */ |
| private static class AddTargetControl extends AddAnnotationControl<Target, ElementType> { |
| private static final String SETTINGS_SECTION_NAME= "AddTarget"; //$NON-NLS-1$ |
| |
| /** |
| * Ordinals of enum values that were added in Java 8. As we are still on Java 7 here, they |
| * cannot be expressed as literals. |
| */ |
| private static final int[] fEnumValuesSinceJava8= new int[] { 8, 9 }; |
| |
| public AddTargetControl() { |
| super(SWT.CHECK, NewWizardMessages.NewAnnotationWizardPage_add_target, Target.class, ElementType.class, 3); |
| } |
| |
| @Override |
| protected void updateAvailableButtons() { |
| boolean isJava8orHigher= JavaModelUtil.is1d8OrHigher(fJavaProject); |
| for (int index : fEnumValuesSinceJava8) { |
| fEnumButtons.enableSelectionButton(index, isJava8orHigher); |
| } |
| } |
| |
| @Override |
| protected String dialogSettingsSectionName() { |
| return SETTINGS_SECTION_NAME; |
| } |
| } |
| |
| /* |
| * @see WizardPage#createControl |
| */ |
| @Override |
| public void createControl(Composite parent) { |
| initializeDialogUnits(parent); |
| |
| Composite composite= new Composite(parent, SWT.NONE); |
| |
| int nColumns= 4; |
| |
| GridLayout layout= new GridLayout(); |
| layout.numColumns= nColumns; |
| composite.setLayout(layout); |
| |
| createContainerControls(composite, nColumns); |
| createPackageControls(composite, nColumns); |
| createEnclosingTypeControls(composite, nColumns); |
| |
| createSeparator(composite, nColumns); |
| |
| createTypeNameControls(composite, nColumns); |
| createModifierControls(composite, nColumns); |
| |
| createSeparator(composite, nColumns); |
| |
| createAddAnnotationControls(composite, nColumns); |
| |
| createSeparator(composite, nColumns); |
| |
| createCommentControls(composite, nColumns); |
| enableCommentControl(true); |
| |
| setControl(composite); |
| |
| Dialog.applyDialogFont(composite); |
| PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IJavaHelpContextIds.NEW_ANNOTATION_WIZARD_PAGE); |
| } |
| |
| private void createAddAnnotationControls(Composite composite, int nColumns) { |
| fRetentionSelection.doFillIntoGrid(composite, nColumns); |
| DialogField.createEmptySpace(composite, nColumns); |
| fTargetSelection.doFillIntoGrid(composite, nColumns); |
| DialogField.createEmptySpace(composite, nColumns); |
| fDocumentedSelection.doFillIntoGrid(composite, nColumns); |
| } |
| |
| /* |
| * @see WizardPage#becomesVisible |
| */ |
| @Override |
| public void setVisible(boolean visible) { |
| super.setVisible(visible); |
| if (visible) { |
| setFocus(); |
| } |
| } |
| |
| // ------ code generation -------- |
| |
| @Override |
| protected void createTypeMembers(IType newType, ImportsManager imports, IProgressMonitor monitor) throws CoreException { |
| String lineDelimiter= StubUtility.getLineDelimiterUsed(newType.getJavaProject()); |
| |
| fTargetSelection.addAnnotation(newType, imports, lineDelimiter); |
| fRetentionSelection.addAnnotation(newType, imports, lineDelimiter); |
| addDocumentedAnnotation(newType, imports, lineDelimiter); |
| |
| persistSettings(); |
| } |
| |
| private void addDocumentedAnnotation(IType newType, ImportsManager imports, String lineDelimiter) throws JavaModelException { |
| if (fDocumentedSelection.isSelected()) { |
| String typeName= imports.addImport(Documented.class.getName()); |
| int start= newType.getSourceRange().getOffset(); |
| IBuffer buffer= newType.getCompilationUnit().getBuffer(); |
| buffer.replace(start, 0, "@" + typeName + lineDelimiter); //$NON-NLS-1$ |
| } |
| } |
| |
| private void persistSettings() { |
| IDialogSettings dialogSettings= getDialogSettings(); |
| if (dialogSettings != null) { |
| IDialogSettings section= dialogSettings.getSection(PAGE_NAME); |
| if (section == null) { |
| section= dialogSettings.addNewSection(PAGE_NAME); |
| } |
| section.put(SETTINGS_ADD_DOCUMENTED, fDocumentedSelection.isSelected()); |
| fRetentionSelection.persistSettings(section); |
| fTargetSelection.persistSettings(section); |
| } |
| } |
| } |