/** * Copyright (C) 2005 - 2011 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.jdt.command.correct; import java.lang.reflect.Field; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.eclim.Services; import org.eclim.annotation.Command; import org.eclim.command.CommandLine; import org.eclim.command.Options; import org.eclim.plugin.core.command.AbstractCommand; import org.eclim.plugin.jdt.util.JavaUtils; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.ui.text.correction.AssistContext; import org.eclipse.jdt.internal.ui.text.correction.proposals.CUCorrectionProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.CorrectPackageDeclarationProposal; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.IProblemLocation; import org.eclipse.jdt.ui.text.java.IQuickFixProcessor; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; /** * Handles requests for code correction. * * @author Eric Van Dewoestine */ @Command( name = "java_correct", options = "REQUIRED p project ARG," + "REQUIRED f file ARG," + "REQUIRED l line ARG," + "REQUIRED o offset ARG," + "OPTIONAL e encoding ARG," + "OPTIONAL a apply ARG" ) public class CodeCorrectCommand extends AbstractCommand { /** * {@inheritDoc} */ public Object execute(CommandLine commandLine) throws Exception { String file = commandLine.getValue(Options.FILE_OPTION); String projectName = commandLine.getValue(Options.PROJECT_OPTION); int line = commandLine.getIntValue(Options.LINE_OPTION); int offset = getOffset(commandLine); // JavaUtils refreshes the file when getting it. ICompilationUnit src = JavaUtils.getCompilationUnit(projectName, file); IProblem problem = getProblem(src, line, offset); if(problem == null){ String message = Services.getMessage("error.not.found", file, line); if(commandLine.hasOption(Options.APPLY_OPTION)){ throw new RuntimeException(message); } return message; } List<IJavaCompletionProposal> proposals = getProposals(src, problem); if(commandLine.hasOption(Options.APPLY_OPTION)){ IJavaCompletionProposal proposal = (IJavaCompletionProposal) proposals.get(commandLine.getIntValue(Options.APPLY_OPTION)); // does not work since it is so deeply tied to the ui (grabbing the // editor, opening dialogs, etc.). //proposal.apply(JavaUtils.getDocument(src)); return proposal.toString(); } HashMap<String,Object> result = new HashMap<String,Object>(); result.put("message", problem.getMessage()); result.put("offset", problem.getSourceStart()); result.put("corrections", getCorrections(proposals)); return result; } /** * Gets the requested problem. * * @param src The source file. * @param line The line number of the error. * @param offset The offset of the error. * @return The IProblem or null if none found. */ protected IProblem getProblem(ICompilationUnit src, int line, int offset) throws Exception { IProblem[] problems = JavaUtils.getProblems(src); ArrayList<IProblem> errors = new ArrayList<IProblem>(); for(int ii = 0; ii < problems.length; ii++){ if(problems[ii].getSourceLineNumber() == line){ errors.add(problems[ii]); } } IProblem problem = null; if(errors.size() == 0){ return null; }else if(errors.size() > 0){ for (IProblem p : errors){ if(offset < p.getSourceStart() && offset <= p.getSourceEnd()){ problem = p; } } } if(problem == null){ problem = (IProblem)errors.get(0); } return problem; } /** * Gets possible corrections for the supplied problem. * * @param src The src file. * @param problem The problem. * @return Returns a List of IJavaCompletionProposal. */ protected List<IJavaCompletionProposal> getProposals( ICompilationUnit src, IProblem problem) throws Exception { ArrayList<IJavaCompletionProposal> results = new ArrayList<IJavaCompletionProposal>(); int length = problem.getSourceEnd() - problem.getSourceStart(); AssistContext context = new AssistContext( src, problem.getSourceStart(), length); IProblemLocation[] locations = new IProblemLocation[]{new ProblemLocation(problem)}; IQuickFixProcessor[] processors = JavaUtils.getQuickFixProcessors(src); for(int ii = 0; ii < processors.length; ii++){ if (processors[ii] != null && processors[ii].hasCorrections(src, problem.getID())) { // we currently don't support the ajdt processor since it relies on // PlatformUI.getWorkbench().getActiveWorkbenchWindow() which is null // here. if (processors[ii].getClass().getName().equals( "org.eclipse.ajdt.internal.ui.editor.quickfix.QuickFixProcessor")) { continue; } IJavaCompletionProposal[] proposals = processors[ii].getCorrections(context, locations); if(proposals != null){ for (IJavaCompletionProposal proposal : proposals){ if (!(proposal instanceof CUCorrectionProposal)){ continue; } CUCorrectionProposal cuProposal = (CUCorrectionProposal)proposal; // for now we aren't going to support changes to files other than the // current one. if (!src.equals(cuProposal.getCompilationUnit())){ continue; } // filter out corrections that have no preview, since they can't be // applied in the same fashion as those that have previews. String preview = proposal.getAdditionalProposalInfo(); if (preview == null || preview.trim().equals("") || preview.trim().startsWith("Start the") || preview.trim().startsWith("Opens") || preview.trim().startsWith("Evaluates") || preview.trim().startsWith("<p>Move")) { continue; } // hack to fix off by one issue w/ package correction proposal if (cuProposal instanceof CorrectPackageDeclarationProposal){ TextChange change = cuProposal.getTextChange(); TextEdit edit = change.getEdit(); if (edit instanceof MultiTextEdit){ Field fChildren = TextEdit.class.getDeclaredField("fChildren"); fChildren.setAccessible(true); @SuppressWarnings("unchecked") List<TextEdit> children = (List<TextEdit>)fChildren.get(edit); edit = children.get(children.size() - 1); } // the InsertEdit version is fine, the ReplaceEdit is off by one. if (edit instanceof ReplaceEdit){ Field flength = TextEdit.class.getDeclaredField("fLength"); flength.setAccessible(true); flength.setInt(edit, edit.getLength() + 1); } } results.add(cuProposal); } } } } final Collator collator = Collator.getInstance(); Collections.sort(results, new Comparator<IJavaCompletionProposal>(){ public int compare(IJavaCompletionProposal p1, IJavaCompletionProposal p2){ int r1 = p1.getRelevance(); int r2 = p2.getRelevance(); if (r1 == r2){ // used as a determanistic tie breaker. return collator.compare(p1.getDisplayString(), p2.getDisplayString()); } // higher number is more relavant. return r2 - r1; } public boolean equals(Object obj){ return false; } }); return results; } /** * Converts the supplied list of IJavaCompletionProposal(s) to array of * CodeCorrectResult. * * @param proposals List of IJavaCompletionProposal. * @return Array of CodeCorrectResult. */ protected List<CodeCorrectResult> getCorrections( List<IJavaCompletionProposal> proposals) throws Exception { ArrayList<CodeCorrectResult> corrections = new ArrayList<CodeCorrectResult>(); Iterator<IJavaCompletionProposal> iterator = proposals.iterator(); for(int ii = 0; iterator.hasNext(); ii++){ IJavaCompletionProposal proposal = iterator.next(); String preview = null; /*if (proposal instanceof CorrectPackageDeclarationProposal){ preview = getAdditionalProposalInfo((CUCorrectionProposal)proposal); }else{*/ preview = proposal.getAdditionalProposalInfo(); //} preview = preview .replaceAll("<br>", "\n") .replaceAll("<.+?>", ""); corrections.add(new CodeCorrectResult( ii, proposal.getDisplayString(), preview)); } return corrections; } }