diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java index 1906a948737..df109c5377f 100644 --- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java +++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java @@ -76,6 +76,8 @@ private CorrectionMessages() { public static String QuickAssistProcessor_name_extension_from_interface; public static String QuickAssistProcessor_convert_constant_name; public static String QuickAssistProcessor_convert_constant_name_description; + public static String QuickAssistProcessor_convert_project_name; + public static String QuickAssistProcessor_convert_project_name_description; public static String SerialVersionHashOperation_computing_id; public static String SerialVersionHashOperation_error_classnotfound; public static String SerialVersionHashOperation_save_caption; diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties index 54f7b19fc72..054d9fa7cc4 100644 --- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties +++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties @@ -379,6 +379,8 @@ QuickAssistProcessor_name_extension_from_interface={0}Extension QuickAssistProcessor_convert_constant_name=Rename to follow constant naming convention QuickAssistProcessor_convert_constant_name_description=Rename the field {0} to {1} +QuickAssistProcessor_convert_project_name=Rename to follow project naming convention +QuickAssistProcessor_convert_project_name_description=Rename the field {0} to {1} QuickAssistProcessor_unwrap_ifstatement=Remove surrounding 'if' statement QuickAssistProcessor_unwrap_whilestatement=Remove surrounding 'while' statement QuickAssistProcessor_unwrap_forstatement=Remove surrounding 'for' statement diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java index ab94f2b0031..2b7d9134812 100644 --- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java +++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java @@ -247,6 +247,7 @@ public interface IProposalRelevance { int CONVERT_TO_ANONYMOUS_CLASS_CREATION= 2; int CONVERT_TO_SWITCH_EXPRESSION= 2; int FIELD_NAMING_CONVENTION= 2; + int PROJECT_NAMING_CONVENTION= 1; int JOIN_VARIABLE_DECLARATION= 1; int INVERT_EQUALS= 1; int CONVERT_TO_ITERATOR_FOR_LOOP= 1; diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java index 7627b93b8bb..4147c9b0206 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java @@ -226,6 +226,7 @@ import org.eclipse.jdt.internal.ui.text.correction.proposals.AddStaticFavoriteProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.AssignToVariableAssistProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.ConvertFieldNamingConventionProposal; +import org.eclipse.jdt.internal.ui.text.correction.proposals.ConvertProjectNamingConventionProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.GenerateForLoopAssistProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposal; @@ -283,8 +284,12 @@ public class QuickAssistProcessor implements IQuickAssistProcessor { public static final String FIELD_NAMING_CONVENTION_ID= "org.eclipse.jdt.ui.correction.convertToConstantNamingConvention.assist"; //$NON-NLS-1$ + public static final String CONVERT_To_PROJECT_NAMING_CONVENTION_ID= "org.eclipse.jdt.ui.correction.convertToProjectNamingConvention.assist"; //$NON-NLS-1$ + public static final Pattern FIELD_NAMING_PATTERN= Pattern.compile("^[A-Z0-9]+(_[A-Z0-9]+)*$"); //$NON-NLS-1$ + public static final Pattern PROJECT_NAMING_PATTERN= Pattern.compile("([a-z]+|[A-Z][a-z]*|\\d+|_+)"); //$NON-NLS-1$ + public QuickAssistProcessor() { super(); } @@ -323,6 +328,7 @@ public boolean hasAssists(IInvocationContext context) throws CoreException { || getExtractMethodFromLambdaProposal(context, coveringNode, false, null) || getInlineLocalProposal(context, coveringNode, null) || getConvertFieldNamingConventionProposal(context, coveringNode, null) + || getConvertProjectNamingConvention(context, coveringNode, null) || getConvertLocalToFieldProposal(context, coveringNode, null) || getConvertAnonymousToNestedProposal(context, coveringNode, null) || getConvertAnonymousClassCreationsToLambdaProposals(context, coveringNode, null) @@ -399,6 +405,7 @@ public IJavaCompletionProposal[] getAssists(IInvocationContext context, IProblem getExtractMethodFromLambdaProposal(context, coveringNode, problemsAtLocation, resultingCollections); getInlineLocalProposal(context, coveringNode, resultingCollections); getConvertFieldNamingConventionProposal(context, coveringNode, resultingCollections); + getConvertProjectNamingConvention(context, coveringNode, resultingCollections); getConvertLocalToFieldProposal(context, coveringNode, resultingCollections); getConvertAnonymousToNestedProposal(context, coveringNode, resultingCollections); getConvertAnonymousClassCreationsToLambdaProposals(context, coveringNode, resultingCollections); @@ -4660,6 +4667,131 @@ public static String convertToConstantName(String identifier) { } return constantNaming.toString().replaceAll("_{2,}", "_"); //$NON-NLS-1$ //$NON-NLS-2$ } + private boolean getConvertProjectNamingConvention(IInvocationContext context, ASTNode coveringNode, ArrayList resultingCollections) { + if (!(coveringNode instanceof SimpleName)) { + return false; + } + SimpleName simpleName= (SimpleName) coveringNode; + String selectedField= simpleName.toString(); + int offset= coveringNode.getStartPosition(); + if (simpleName.getAST().apiLevel() >= ASTHelper.JLS10 && simpleName.isVar()) { + return false; + } + IEditorPart editor= ((AssistContext) context).getEditor(); + if (!(editor instanceof JavaEditor)) + return false; + + if (!(simpleName.resolveBinding() instanceof IVariableBinding binding) || !binding.isField()) { + return false; + } + + CompilationUnit cu= context.getASTRoot(); + VariableDeclarationFragment vdf= (VariableDeclarationFragment) ASTNodes.findDeclaration(binding, cu); + if (vdf == null) { + return false; + } + + FieldDeclaration fd= (FieldDeclaration) vdf.getParent(); + if (fd == null) { + return false; + } + + AbstractTypeDeclaration typeDecl= (AbstractTypeDeclaration) fd.getParent(); + if (typeDecl == null) { + return false; + } + + FieldDeclarationChecker fdChecker= new FieldDeclarationChecker(); + typeDecl.accept(fdChecker); + + List fieldNames= fdChecker.getFieldDeclarationList(); + if (fieldNames.size() < 3) { + return false; + } + + List namePrefix= new ArrayList<>(); + for (int i= 0; i < fieldNames.size(); i++) { + namePrefix.add(splitField(fieldNames.get(i)).get(0)); + } + + String firstField= findMostFrequentElement(namePrefix); + List selectField= splitField(selectedField); + if (firstField == null || selectField.contains(firstField)) { + return false; + } + + selectField.add(0, firstField); + String newName= joinToCamelCase(selectField); + if (fdChecker.getFieldDeclarationList().contains(newName)) { + return false; + } + + if (resultingCollections == null) { + return true; + } + + IField iField= (IField) binding.getJavaElement(); + Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_RENAME); + ConvertProjectNamingConventionProposal proposal= new ConvertProjectNamingConventionProposal(newName, offset, offset, image, iField); + proposal.setCommandId(CONVERT_To_PROJECT_NAMING_CONVENTION_ID); + resultingCollections.add(proposal); + + return true; + } + + public static List splitField(String input) { + List words= new ArrayList<>(); + Pattern pattern= PROJECT_NAMING_PATTERN; + Matcher matcher= pattern.matcher(input); + while (matcher.find()) { + String word= matcher.group(); + words.add(word); + } + return words; + } + + public static String joinToCamelCase(List elements) { + if (elements == null || elements.isEmpty()) { + return null; + } + StringBuilder result= new StringBuilder(); + result.append(elements.get(0)); + String camelCaseElement= null; + for (int i= 1; i < elements.size(); i++) { + String element= elements.get(i); + if (i - 1 >= 0 && elements.get(i - 1).equals("_")) { //$NON-NLS-1$ + camelCaseElement= element; + } else { + camelCaseElement= element.substring(0, 1).toUpperCase() + element.substring(1).toLowerCase(); + } + result.append(camelCaseElement); + } + return result.toString(); + } + + @SuppressWarnings("boxing") + public static String findMostFrequentElement(List list) { + if (list.isEmpty()) { + return null; + } + Map elementCountMap= new HashMap<>(); + for (String element : list) { + elementCountMap.put(element, elementCountMap.getOrDefault(element, 0) + 1); + } + String mostFrequentElement= null; + int maxCount= 0; + for (Map.Entry entry : elementCountMap.entrySet()) { + if (entry.getValue() > maxCount) { + mostFrequentElement= entry.getKey(); + maxCount= entry.getValue(); + } + } + if (maxCount >= (list.size()/2 -1)) { + return mostFrequentElement; + } else { + return null; + } + } } final class FieldDeclarationChecker extends ASTVisitor { diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/ConvertProjectNamingConventionProposal.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/ConvertProjectNamingConventionProposal.java new file mode 100644 index 00000000000..d32760fb9e4 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/ConvertProjectNamingConventionProposal.java @@ -0,0 +1,119 @@ +package org.eclipse.jdt.internal.ui.text.correction.proposals; + +import java.text.MessageFormat; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.contentassist.IContextInformation; + +import org.eclipse.ltk.core.refactoring.CheckConditionsOperation; +import org.eclipse.ltk.core.refactoring.PerformRefactoringOperation; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; + +import org.eclipse.jdt.core.IField; + +import org.eclipse.jdt.internal.corext.refactoring.rename.RenameFieldProcessor; + +import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; +import org.eclipse.jdt.ui.text.java.correction.ICommandAccess; + +import org.eclipse.jdt.internal.ui.text.correction.CorrectionMessages; +import org.eclipse.jdt.internal.ui.text.correction.IProposalRelevance; + +public class ConvertProjectNamingConventionProposal implements IJavaCompletionProposal, ICommandAccess { + private String replacement; + + private int offset; + + private int cursorPosition; + + private int fRelevance; + + private String fCommandId; + + private Image image; + + private IField fSelectedField; + + public ConvertProjectNamingConventionProposal(String replacement, int offset, int cursorPosition, Image image, IField selectedField) { + this.replacement= replacement; + this.offset= offset; + this.cursorPosition= cursorPosition; + this.fRelevance= IProposalRelevance.PROJECT_NAMING_CONVENTION; + this.image= image; + this.fSelectedField= selectedField; + } + + @Override + public void apply(IDocument document) { + try { + RenameFieldProcessor processor= new RenameFieldProcessor(fSelectedField); + processor.setNewElementName(replacement); + processor.setUpdateReferences(true); + processor.setRenameSetter(true); + processor.setRenameGetter(true); + processor.setUpdateTextualMatches(true); + RenameRefactoring refactoring= new RenameRefactoring(processor); + RefactoringStatus status; + status= refactoring.checkAllConditions(new NullProgressMonitor()); + if (status.isOK()) { + PerformRefactoringOperation operation= new PerformRefactoringOperation(refactoring, CheckConditionsOperation.ALL_CONDITIONS); + operation.run(new NullProgressMonitor()); + } + } catch (OperationCanceledException e) { + e.printStackTrace(); + } catch (CoreException e) { + e.printStackTrace(); + } + } + + @Override + public Point getSelection(IDocument document) { + return new Point(offset + cursorPosition, 0); + } + + @Override + public String getAdditionalProposalInfo() { + return MessageFormat.format(CorrectionMessages.QuickAssistProcessor_convert_project_name_description, fSelectedField.getElementName(), replacement); + } + + @Override + public String getDisplayString() { + return CorrectionMessages.QuickAssistProcessor_convert_project_name; + } + + @Override + public Image getImage() { + return image; + } + + @Override + public IContextInformation getContextInformation() { + return null; + } + + @Override + public int getRelevance() { + return fRelevance; + } + + public void setRelevance(int relevance) { + fRelevance= relevance; + } + + @Override + public String getCommandId() { + return fCommandId; + } + + public void setCommandId(String commandId) { + fCommandId= commandId; + } +}