/******************************************************************************* * Copyright (c) 2006, 2015 Zend Technologies 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 * * Contributors: * Zend Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.php.refactoring.core.rename.logic; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.ltk.core.refactoring.TextEditChangeGroup; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.php.core.ast.nodes.*; import org.eclipse.php.core.ast.visitor.AbstractVisitor; import org.eclipse.php.internal.ui.search.text.TextSearcher; import org.eclipse.php.internal.ui.search.text.TextSearcherFactory; import org.eclipse.php.refactoring.core.PhpRefactoringCoreMessages; import org.eclipse.php.refactoring.core.changes.ProgramFileChange; import org.eclipse.search.internal.ui.text.FileMatch; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.TextEditGroup; /** * Base class for all logics rename * * @author Roy, 2007 */ @SuppressWarnings("restriction") public abstract class AbstractRename extends AbstractVisitor { /** * The global variable name to change */ protected String oldName; /** * {@link TextEditGroup} */ protected final List<TextEditGroup> groups = new LinkedList<TextEditGroup>(); /** * The file we are working on */ protected final IFile changedFile; /** * The new name of the variable */ protected final String newName; /** * Wether or not to search strings and comments */ protected final boolean searchTextual; /** * Represents the global context */ protected final static String GLOBAL = "Global"; //$NON-NLS-1$ /** * Pattern used in scalar search */ private final Pattern pattern; /** * @param file * @param oldName * @param newName */ public AbstractRename(IFile file, String oldName, String newName, boolean searchTextual) { if (newName == null || file == null || oldName == null) { throw new IllegalArgumentException(); } this.oldName = oldName; this.newName = newName; this.changedFile = file; this.searchTextual = searchTextual; this.pattern = Pattern.compile(getTextualSearchPattern()); } /** * Adds the identifier to the list * * @param identifier */ protected void addChange(Identifier identifier) { addChange(identifier.getStart()); } /** * Adds the scalar to the list * * @param scalar */ protected void addChange(Scalar scalar) { final char charAt = scalar.getStringValue().charAt(0); final int isQuotedOffset = charAt == '"' || charAt == '\'' ? 1 : 0; addChange(scalar.getStart() + isQuotedOffset); } /** * Adds the scalar to the list * * @param scalar */ protected void addChange(int start) { addChange(start, getRenameDescription()); } public boolean hasChanges() { return groups.size() != 0; } public void updateChange(TextFileChange change) { // check for empty changes if (!hasChanges()) { return; } addGroups(change, groups); } /** * Adds the edit groups to an existing change * * @param change * - the change that will be used as a container * @param groups * - the groups to add */ private final static void addGroups(TextFileChange change, List<TextEditGroup> groups) { assert change != null && groups != null; TextEditChangeGroup[] textEditChangeGroups = change.getTextEditChangeGroups(); OUTER: for (TextEditGroup editGroup : groups) { TextEditChangeGroup textEditChangeGroup = new TextEditChangeGroup(change, editGroup); final TextEdit textEdit = editGroup.getTextEdits()[0]; for (TextEditChangeGroup existingTextEditChangeGroup : textEditChangeGroups) { TextEdit existingTextEdit = existingTextEditChangeGroup.getTextEdits()[0]; if (existingTextEdit.getOffset() == textEdit.getOffset()) { // avoid // overlapping // edits continue OUTER; } } change.addTextEditChangeGroup(textEditChangeGroup); change.addEdit(textEdit); } } /** * Merges the groups to an existing change * * @param change */ public final void mergeGroups(ProgramFileChange change) { addGroups(change, this.groups); } /** * */ public boolean visit(Program program) { final List<Statement> statements = program.statements(); for (Statement element : statements) { element.accept(this); } if (this.searchTextual) { searchTextualOccurrences(program); } return false; } protected void searchTextualOccurrences(Program program) { TextSearcher searcher = TextSearcherFactory.createSearcher(changedFile, getTextualSearchPattern()); searcher.search(null); /** * an iterator of @link FileMatch */ final Iterator<?> searchIterator = searcher.getResults().iterator(); FileMatch currentMatch = (FileMatch) (searchIterator.hasNext() ? searchIterator.next() : null); /** * an iterator of @link Comment */ final Iterator<Comment> commentIterator = program.comments().iterator(); Comment currentComment = (Comment) (commentIterator.hasNext() ? commentIterator.next() : null); while (currentComment != null && currentMatch != null) { while (isInside(currentComment, currentMatch)) { // if the comment abs the text - add change if (textInsideComment(currentComment, currentMatch)) { addChange(currentMatch.getOffset() + 1, PhpRefactoringCoreMessages.getString("AbstractRename_0")); //$NON-NLS-1$ } currentMatch = (FileMatch) (searchIterator.hasNext() ? searchIterator.next() : null); } while (isCommentBefore(currentMatch, currentComment)) { currentComment = (Comment) (commentIterator.hasNext() ? commentIterator.next() : null); } while (isMatchBefore(currentMatch, currentComment)) { currentMatch = (FileMatch) (searchIterator.hasNext() ? searchIterator.next() : null); } } } protected void addChange(int start, String name) { final TextEditGroup textEditGroup = new TextEditGroup(name); final ReplaceEdit replaceEdit = new ReplaceEdit(start, oldName.length(), this.newName); textEditGroup.addTextEdit(replaceEdit); groups.add(textEditGroup); } protected String getTextualSearchPattern() { return "\\W" + Pattern.quote(oldName) + "\\W"; //$NON-NLS-1$ //$NON-NLS-2$ } private final boolean isMatchBefore(FileMatch currentMatch, Comment currentComment) { return currentComment != null && currentMatch != null && currentMatch.getOffset() + currentMatch.getLength() <= currentComment.getStart(); } private final boolean isCommentBefore(FileMatch currentMatch, Comment currentComment) { return currentComment != null && currentMatch != null && currentComment.getEnd() <= currentMatch.getOffset(); } protected final boolean isInside(Comment currentComment, FileMatch currentMatch) { return currentComment != null && currentMatch != null && currentComment.getStart() <= currentMatch.getOffset() && currentComment.getEnd() >= currentMatch.getOffset(); } protected boolean textInsideComment(final Comment currentComment, final FileMatch currentMatch) { return currentComment.getStart() <= currentMatch.getOffset() + 1 && currentComment.getEnd() > currentMatch.getOffset() + 1; } public boolean visit(Scalar scalar) { if (searchTextual) { final String stringValue = scalar.getStringValue(); if (scalar.getScalarType() == Scalar.TYPE_STRING && stringValue != null) { final Matcher matcher = pattern.matcher(stringValue); while (matcher.find()) { addChange(scalar.getStart() + matcher.start() + matcher.group().indexOf(oldName), PhpRefactoringCoreMessages.getString("AbstractRename_0")); //$NON-NLS-1$ } } } return true; } public abstract String getRenameDescription(); }