| /******************************************************************************* |
| * Copyright (c) 2011, 2016 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.jdt.internal.ui.propertiesfileeditor; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Shell; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| |
| import org.eclipse.core.resources.IFile; |
| |
| import org.eclipse.text.edits.TextEdit; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.BadPartitioningException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentExtension3; |
| import org.eclipse.jface.text.ITypedRegion; |
| import org.eclipse.jface.text.contentassist.ICompletionProposal; |
| import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; |
| import org.eclipse.jface.text.source.ISourceViewer; |
| |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.CompositeChange; |
| import org.eclipse.ltk.core.refactoring.TextChange; |
| |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IField; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.ITypeHierarchy; |
| import org.eclipse.jdt.core.JavaModelException; |
| |
| import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; |
| import org.eclipse.jdt.internal.corext.refactoring.RefactoringExecutionStarter; |
| import org.eclipse.jdt.internal.corext.refactoring.nls.AccessorClassModifier; |
| import org.eclipse.jdt.internal.corext.refactoring.nls.NLSPropertyFileModifier; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds; |
| import org.eclipse.jdt.ui.text.java.correction.CUCorrectionProposal; |
| import org.eclipse.jdt.ui.text.java.correction.ChangeCorrectionProposal; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| import org.eclipse.jdt.internal.ui.JavaPluginImages; |
| import org.eclipse.jdt.internal.ui.text.correction.proposals.EditAnnotator; |
| |
| /** |
| * The properties file quick assist processor. |
| * |
| * @since 3.8 |
| */ |
| public class PropertiesQuickAssistProcessor { |
| |
| public static boolean hasAssists(PropertiesAssistContext invocationContext) { |
| try { |
| return getEscapeUnescapeBackslashProposals(invocationContext, null) || |
| getCreateFieldsInAccessorClassProposals(invocationContext, null) || |
| getRemovePropertiesProposals(invocationContext, null) || |
| getRenameKeysProposals(invocationContext, null); |
| } catch (BadLocationException | BadPartitioningException e) { |
| JavaPlugin.log(e); |
| } |
| return false; |
| } |
| |
| public static ICompletionProposal[] collectAssists(PropertiesAssistContext invocationContext) throws BadLocationException, BadPartitioningException { |
| ArrayList<ICompletionProposal> resultingCollections= new ArrayList<>(); |
| |
| getEscapeUnescapeBackslashProposals(invocationContext, resultingCollections); |
| getCreateFieldsInAccessorClassProposals(invocationContext, resultingCollections); |
| getRemovePropertiesProposals(invocationContext, resultingCollections); |
| getRenameKeysProposals(invocationContext, resultingCollections); |
| |
| if (resultingCollections.isEmpty()) |
| return null; |
| return resultingCollections.toArray(new ICompletionProposal[resultingCollections.size()]); |
| } |
| |
| private static boolean getEscapeUnescapeBackslashProposals(IQuickAssistInvocationContext invocationContext, ArrayList<ICompletionProposal> resultingCollections) throws BadLocationException, |
| BadPartitioningException { |
| ISourceViewer sourceViewer= invocationContext.getSourceViewer(); |
| IDocument document= sourceViewer.getDocument(); |
| Point selectedRange= sourceViewer.getSelectedRange(); |
| int selectionOffset= selectedRange.x; |
| int selectionLength= selectedRange.y; |
| int proposalOffset; |
| int proposalLength; |
| String text; |
| if (selectionLength == 0) { |
| if (selectionOffset != document.getLength()) { |
| char ch= document.getChar(selectionOffset); |
| if (ch == '=' || ch == ':') { //see PropertiesFilePartitionScanner() |
| return false; |
| } |
| } |
| |
| ITypedRegion partition= null; |
| if (document instanceof IDocumentExtension3) |
| partition= ((IDocumentExtension3)document).getPartition(IPropertiesFilePartitions.PROPERTIES_FILE_PARTITIONING, invocationContext.getOffset(), false); |
| if (partition == null) |
| return false; |
| |
| String type= partition.getType(); |
| if (!type.equals(IPropertiesFilePartitions.PROPERTY_VALUE) |
| && !type.equals(IDocument.DEFAULT_CONTENT_TYPE)) { |
| return false; |
| } |
| proposalOffset= partition.getOffset(); |
| proposalLength= partition.getLength(); |
| text= document.get(proposalOffset, proposalLength); |
| |
| if (type.equals(IPropertiesFilePartitions.PROPERTY_VALUE)) { |
| text= text.substring(1); //see PropertiesFilePartitionScanner() |
| proposalOffset++; |
| proposalLength--; |
| } |
| } else { |
| proposalOffset= selectionOffset; |
| proposalLength= selectionLength; |
| text= document.get(proposalOffset, proposalLength); |
| } |
| |
| if (PropertiesFileEscapes.containsUnescapedBackslash(text)) { |
| if (resultingCollections == null) |
| return true; |
| resultingCollections.add(new EscapeBackslashCompletionProposal(PropertiesFileEscapes.escape(text, false, true, false), proposalOffset, proposalLength, |
| PropertiesFileEditorMessages.EscapeBackslashCompletionProposal_escapeBackslashes)); |
| return true; |
| } |
| if (PropertiesFileEscapes.containsEscapedBackslashes(text)) { |
| if (resultingCollections == null) |
| return true; |
| resultingCollections.add(new EscapeBackslashCompletionProposal(PropertiesFileEscapes.unescapeBackslashes(text), proposalOffset, proposalLength, |
| PropertiesFileEditorMessages.EscapeBackslashCompletionProposal_unescapeBackslashes)); |
| return true; |
| } |
| return false; |
| } |
| |
| private static boolean getCreateFieldsInAccessorClassProposals(PropertiesAssistContext invocationContext, ArrayList<ICompletionProposal> resultingCollections) |
| throws BadLocationException, BadPartitioningException { |
| IDocument document= invocationContext.getDocument(); |
| int selectionOffset= invocationContext.getOffset(); |
| int selectionLength= invocationContext.getLength(); |
| List<String> fields= new ArrayList<>(); |
| |
| IType accessorClass= invocationContext.getAccessorType(); |
| if (accessorClass == null || !isEclipseNLSUsed(accessorClass)) |
| return false; |
| |
| List<String> keys= getKeysFromSelection(document, selectionOffset, selectionLength); |
| if (keys == null || keys.isEmpty()) |
| return false; |
| |
| for (String key : keys) { |
| if (!isValidJavaIdentifier(key)) |
| continue; |
| IField field= accessorClass.getField(key); |
| if (field.exists()) |
| continue; |
| if (resultingCollections == null) |
| return true; |
| fields.add(key); |
| } |
| |
| if (fields.isEmpty()) |
| return false; |
| |
| ICompilationUnit cu= accessorClass.getCompilationUnit(); |
| try { |
| Change change= AccessorClassModifier.addFields(cu, fields); |
| String name= Messages.format(fields.size() == 1 ? PropertiesFileEditorMessages.PropertiesCorrectionProcessor_create_field_in_accessor_label : PropertiesFileEditorMessages.PropertiesCorrectionProcessor_create_fields_in_accessor_label, BasicElementLabels.getFileName(cu)); |
| resultingCollections.add(new CUCorrectionProposal(name, cu, (TextChange) change, 5, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE))); |
| } catch (CoreException e) { |
| JavaPlugin.log(e); |
| return false; |
| } |
| return true; |
| } |
| |
| private static boolean isValidJavaIdentifier(String key) { |
| if (!Character.isJavaIdentifierStart(key.charAt(0))) |
| return false; |
| |
| for (int i= 1, length= key.length(); i < length; i++) { |
| if (!Character.isJavaIdentifierPart(key.charAt(i))) |
| return false; |
| } |
| return true; |
| } |
| |
| private static boolean getRemovePropertiesProposals(PropertiesAssistContext invocationContext, ArrayList<ICompletionProposal> resultingCollections) |
| throws BadLocationException, BadPartitioningException { |
| IDocument document= invocationContext.getDocument(); |
| int selectionOffset= invocationContext.getOffset(); |
| int selectionLength= invocationContext.getLength(); |
| List<String> fields= new ArrayList<>(); |
| |
| IFile file= invocationContext.getFile(); |
| if (file == null) |
| return false; |
| |
| IType accessorClass= invocationContext.getAccessorType(); |
| if (accessorClass == null || !isEclipseNLSUsed(accessorClass)) |
| return false; |
| |
| List<String> keys= getKeysFromSelection(document, selectionOffset, selectionLength); |
| if (keys == null || keys.isEmpty()) |
| return false; |
| if (resultingCollections == null) |
| return true; |
| |
| for (String key : keys) { |
| IField field= accessorClass.getField(key); |
| if (field.exists()) |
| fields.add(key); |
| } |
| |
| ICompilationUnit cu= accessorClass.getCompilationUnit(); |
| try { |
| Change propertiesFileChange= NLSPropertyFileModifier.removeKeys(file.getFullPath(), keys); |
| Change[] changes; |
| if (fields.size() > 0) { |
| Change accessorChange= AccessorClassModifier.removeFields(cu, fields); |
| changes= new Change[] { propertiesFileChange, accessorChange }; |
| } else { |
| changes= new Change[] { propertiesFileChange }; |
| } |
| |
| String name= (keys.size() == 1) |
| ? PropertiesFileEditorMessages.PropertiesCorrectionProcessor_remove_property_label |
| : PropertiesFileEditorMessages.PropertiesCorrectionProcessor_remove_properties_label; |
| resultingCollections.add(new RemovePropertiesProposal(name, 4, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE), changes)); |
| } catch (CoreException e) { |
| JavaPlugin.log(e); |
| return false; |
| } |
| return true; |
| } |
| |
| private static boolean getRenameKeysProposals(PropertiesAssistContext invocationContext, ArrayList<ICompletionProposal> resultingCollections) |
| throws BadLocationException, BadPartitioningException { |
| ISourceViewer sourceViewer= invocationContext.getSourceViewer(); |
| IDocument document= invocationContext.getDocument(); |
| int selectionOffset= invocationContext.getOffset(); |
| int selectionLength= invocationContext.getLength(); |
| |
| IType accessorClass= invocationContext.getAccessorType(); |
| if (accessorClass == null || !isEclipseNLSUsed(accessorClass)) |
| return false; |
| |
| List<String> keys= getKeysFromSelection(document, selectionOffset, selectionLength); |
| if (keys == null || keys.size() != 1) |
| return false; |
| |
| IField field= accessorClass.getField(keys.get(0)); |
| if (!field.exists()) |
| return false; |
| if (resultingCollections == null) |
| return true; |
| |
| String name= PropertiesFileEditorMessages.PropertiesCorrectionProcessor_rename_in_workspace; |
| resultingCollections.add(new RenameKeyProposal(name, 5, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE), field, sourceViewer.getTextWidget().getShell())); |
| return true; |
| } |
| |
| private static boolean isEclipseNLSUsed(IType accessor) { |
| IJavaProject javaProject= accessor.getJavaProject(); |
| if (javaProject == null || !javaProject.exists()) |
| return false; |
| |
| try { |
| IType nls= javaProject.findType("org.eclipse.osgi.util.NLS"); //$NON-NLS-1$ |
| if(nls==null) |
| return false; |
| ITypeHierarchy supertypeHierarchy= accessor.newSupertypeHierarchy(null); |
| return supertypeHierarchy.contains(nls); |
| } catch (JavaModelException e) { |
| return false; |
| } |
| } |
| |
| private static List<String> getKeysFromSelection(IDocument document, int selectionOffset, int selectionLength) throws BadLocationException, |
| BadPartitioningException { |
| List<String> keys= new ArrayList<>(); |
| String selection= document.get(selectionOffset, (selectionLength == 0) ? 1 : selectionLength).trim(); |
| if (selection.length() == 0) |
| return null; |
| if (selectionLength == 0) { |
| ITypedRegion partition= null; |
| if (document instanceof IDocumentExtension3) |
| partition= ((IDocumentExtension3) document).getPartition(IPropertiesFilePartitions.PROPERTIES_FILE_PARTITIONING, selectionOffset, false); |
| if (partition == null) |
| return null; |
| |
| String type= partition.getType(); |
| if (!(type.equals(IDocument.DEFAULT_CONTENT_TYPE))) { |
| return null; |
| } |
| String key= document.get(partition.getOffset(), partition.getLength()).trim(); |
| if (key.length() > 0) |
| keys.add(key); |
| } else { |
| int offset= selectionOffset; |
| int endOffset= selectionOffset + selectionLength; |
| |
| while (offset < endOffset) { |
| ITypedRegion partition= null; |
| if (document instanceof IDocumentExtension3) |
| partition= ((IDocumentExtension3) document).getPartition(IPropertiesFilePartitions.PROPERTIES_FILE_PARTITIONING, offset, false); |
| if (partition == null) |
| return null; |
| |
| int partitionOffset= partition.getOffset(); |
| int partitionLength= partition.getLength(); |
| offset= partitionOffset + partitionLength; |
| |
| String type= partition.getType(); |
| if (!(type.equals(IDocument.DEFAULT_CONTENT_TYPE))) { |
| continue; |
| } |
| String key= document.get(partitionOffset, partitionLength).trim(); |
| if (key.length() > 0) |
| keys.add(key); |
| } |
| } |
| return keys; |
| } |
| |
| private static class RemovePropertiesProposal extends ChangeCorrectionProposal { |
| |
| private final Change[] fChanges; |
| |
| protected RemovePropertiesProposal(String name, int relevance, Image image, Change[] changes) { |
| super(name, null, relevance, image); |
| fChanges= changes; |
| } |
| |
| @Override |
| protected Change createChange() throws CoreException { |
| return new CompositeChange(getName(), fChanges); |
| } |
| |
| @Override |
| public Object getAdditionalProposalInfo(IProgressMonitor monitor) { |
| final StringBuffer buf= new StringBuffer(); |
| |
| try { |
| for (Change fChange : fChanges) { |
| if (fChange instanceof TextChange) { |
| TextChange change= (TextChange) fChange; |
| String filename= getFileName(change); |
| if (filename != null) { |
| buf.append("<b>"); //$NON-NLS-1$ |
| buf.append(filename); |
| buf.append("</b>"); //$NON-NLS-1$ |
| buf.append("<br>"); //$NON-NLS-1$ |
| } |
| change.setKeepPreviewEdits(true); |
| IDocument currentContent= change.getCurrentDocument(monitor); |
| TextEdit rootEdit= change.getEdit(); |
| EditAnnotator ea= new EditAnnotator(buf, currentContent) { |
| @Override |
| protected boolean rangeRemoved(TextEdit edit) { |
| return annotateEdit(edit, "<del>", "</del>"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| }; |
| rootEdit.accept(ea); |
| ea.unchangedUntil(currentContent.getLength()); // Final pre-existing region |
| buf.append("<br><br>"); //$NON-NLS-1$ |
| } |
| } |
| |
| } catch (CoreException e) { |
| JavaPlugin.log(e); |
| } |
| return buf.toString(); |
| } |
| |
| private String getFileName(TextChange change) { |
| Object modifiedElement= change.getModifiedElement(); |
| if (modifiedElement instanceof IFile) { |
| return ((IFile) modifiedElement).getName(); |
| } else if (modifiedElement instanceof ICompilationUnit) { |
| return ((ICompilationUnit) modifiedElement).getElementName(); |
| } |
| return null; |
| } |
| } |
| |
| private static class RenameKeyProposal extends ChangeCorrectionProposal { |
| |
| private final IField fField; |
| |
| private final Shell fShell; |
| |
| public RenameKeyProposal(String name, int relevance, Image image, IField field, Shell shell) { |
| super(name, null, relevance, image); |
| fField= field; |
| fShell= shell; |
| } |
| |
| @Override |
| public void apply(IDocument document) { |
| try { |
| RefactoringExecutionStarter.startRenameRefactoring(fField, fShell); |
| } catch (CoreException e) { |
| JavaPlugin.log(e); |
| } |
| } |
| |
| @Override |
| public Object getAdditionalProposalInfo(IProgressMonitor monitor) { |
| return PropertiesFileEditorMessages.PropertiesCorrectionProcessor_rename_in_workspace_description; |
| } |
| |
| @Override |
| public String getCommandId() { |
| return IJavaEditorActionDefinitionIds.RENAME_ELEMENT; |
| } |
| } |
| |
| private PropertiesQuickAssistProcessor() { |
| } |
| } |