1 // Copyright (c) 2005 by Leif Frenzel. All rights reserved.
2 // See http://leiffrenzel.de
3 // modified for phpeclipse.de project by axelcl
4 package net.sourceforge.phpdt.ltk.core;
6 import java.io.ByteArrayOutputStream;
7 import java.io.InputStream;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.Iterator;
11 import java.util.List;
14 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
15 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
16 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
17 import net.sourceforge.phpdt.internal.compiler.parser.SyntaxError;
18 import net.sourceforge.phpdt.internal.ui.util.PHPFileUtil;
19 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
21 import org.eclipse.core.resources.IContainer;
22 import org.eclipse.core.resources.IFile;
23 import org.eclipse.core.resources.IProject;
24 import org.eclipse.core.resources.IResource;
25 import org.eclipse.core.resources.ResourcesPlugin;
26 import org.eclipse.core.runtime.CoreException;
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.IStatus;
29 import org.eclipse.core.runtime.Status;
30 import org.eclipse.ltk.core.refactoring.Change;
31 import org.eclipse.ltk.core.refactoring.CompositeChange;
32 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
33 import org.eclipse.ltk.core.refactoring.TextFileChange;
34 import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
35 import org.eclipse.ltk.core.refactoring.participants.IConditionChecker;
36 import org.eclipse.ltk.core.refactoring.participants.ValidateEditChecker;
37 import org.eclipse.text.edits.MultiTextEdit;
38 import org.eclipse.text.edits.ReplaceEdit;
42 * delegate object that contains the logic used by the processor.
46 public class RenameIdentifierDelegate {
48 // private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$
50 protected final RenameIdentifierInfo info;
52 // PHP file with the identifier to rename -> offset of the key
53 protected final Map phpFiles;
55 public RenameIdentifierDelegate(final RenameIdentifierInfo info) {
57 phpFiles = new HashMap();
60 RefactoringStatus checkInitialConditions() {
61 RefactoringStatus result = new RefactoringStatus();
62 IFile sourceFile = info.getSourceFile();
63 if (sourceFile == null || !sourceFile.exists()) {
64 result.addFatalError(CoreTexts.renamePropertyDelegate_noSourceFile);
65 } else if (info.getSourceFile().isReadOnly()) {
66 result.addFatalError(CoreTexts.renamePropertyDelegate_roFile);
67 } else if (isEmpty(info.getOldName())) {
68 // || !isPropertyKey( info.getSourceFile(), info.getOldName() ) ) {
69 result.addFatalError(CoreTexts.renamePropertyDelegate_noPHPKey);
74 RefactoringStatus checkFinalConditions(final IProgressMonitor pm, final CheckConditionsContext ctxt) {
75 RefactoringStatus result = new RefactoringStatus();
76 pm.beginTask(CoreTexts.renamePropertyDelegate_checking, 100);
77 // do something long-running here: traverse the entire project (or even
78 // workspace) to look for all *.properties files with the same bundle
80 IContainer rootContainer;
81 ArrayList phpProjects = new ArrayList();
83 if (info.isAllProjects()) {
84 rootContainer = ResourcesPlugin.getWorkspace().getRoot();
87 members = rootContainer.members();
88 for (int i = 0; i < members.length; i++) {
89 if (members[i] instanceof IProject) {
90 project = (IProject) members[i];
92 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
93 search(project, result);
95 } catch (CoreException e) {
96 String msg = "Project: " + project.getLocation().toOSString() + " CoreException " + e.getMessage();
98 } catch (Exception e) {
99 String msg = "Project: " + project.getLocation().toOSString() + " Exception " + e.getMessage();
100 result.addError(msg);
104 } catch (CoreException e) {
105 String msg = "Workspace: " + rootContainer.getLocation().toOSString() + " CoreException " + e.getMessage();
106 result.addError(msg);
109 project = info.getSourceFile().getProject();
111 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
112 search(project, result);
114 } catch (CoreException e) {
115 String msg = "Project: " + project.getLocation().toOSString() + " CoreException " + e.getMessage();
116 result.addError(msg);
117 } catch (Exception e) {
118 String msg = "Project: " + project.getLocation().toOSString() + " Exception " + e.getMessage();
119 result.addError(msg);
126 IFile[] files = new IFile[phpFiles.size()];
127 phpFiles.keySet().toArray(files);
128 IConditionChecker checker = ctxt.getChecker(ValidateEditChecker.class);
129 ValidateEditChecker editChecker = (ValidateEditChecker) checker;
130 editChecker.addFiles(files);
136 protected void createChange(final IProgressMonitor pm, final CompositeChange rootChange) {
138 pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges, 100);
139 // all files in the same bundle
140 if (info.isUpdateProject()) {
141 rootChange.addAll(createChangesForContainer(pm));
151 // private Change createRenameChange() {
152 // // create a change object for the file that contains the property the
153 // // user has selected to rename
154 // IFile file = info.getSourceFile();
155 // TextFileChange result = new TextFileChange(file.getName(), file);
156 // // a file change contains a tree of edits, first add the root of them
157 // MultiTextEdit fileChangeRootEdit = new MultiTextEdit();
158 // result.setEdit(fileChangeRootEdit);
160 // // edit object for the text replacement in the file, this is the only child
161 // ReplaceEdit edit = new ReplaceEdit(info.getOffset(),
162 // info.getOldName().length(), info.getNewName());
163 // fileChangeRootEdit.addChild(edit);
167 protected Change[] createChangesForContainer(final IProgressMonitor pm) {
168 List result = new ArrayList();
169 Iterator it = phpFiles.keySet().iterator();
170 int numberOfFiles = phpFiles.size();
171 double percent = 100 / numberOfFiles;
174 while (it.hasNext()) {
175 IFile file = (IFile) it.next();
176 List list = getKeyOffsets(file);
177 if (list != null && list.size() > 0) {
178 TextFileChange tfc = new TextFileChange(file.getName(), file);
179 MultiTextEdit fileChangeRootEdit = new MultiTextEdit();
180 tfc.setEdit(fileChangeRootEdit);
182 // edit object for the text replacement in the file, there could be
185 for (int i = 0; i < list.size(); i++) {
186 edit = new ReplaceEdit(((Integer) list.get(i)).intValue(), info.getOldName().length(), info.getNewName());
187 fileChangeRootEdit.addChild(edit);
191 work = Double.valueOf(++indx * percent).intValue();
194 return (Change[]) result.toArray(new Change[result.size()]);
197 protected boolean isEmpty(final String candidate) {
198 return candidate == null || candidate.trim().length() == 0;
201 // private boolean isPropertyKey( final IFile file, final String candidate ) {
202 // boolean result = false;
204 // Properties props = new Properties();
205 // props.load( file.getContents() );
206 // result = props.containsKey( candidate );
207 // } catch( Exception ex ) {
208 // // ignore this, we just assume this is not a favourable situation
209 // ex.printStackTrace();
214 // // whether the file is a PHP file with the same base name as the
215 // // one we refactor and contains the key that interests us
216 // private boolean isToRefactor(final IFile file) {
217 // return PHPFileUtil.isPHPFile(file);
218 // // && !file.equals( info.getSourceFile() )
219 // // && isPropertyKey( file, info.getOldName() );
222 // private String getBundleBaseName() {
223 // String result = info.getSourceFile().getName();
224 // int underscoreIndex = result.indexOf( '_' );
225 // if( underscoreIndex != -1 ) {
226 // result = result.substring( 0, underscoreIndex );
228 // int index = result.indexOf( EXT_PROPERTIES ) - 1;
229 // result = result.substring( 0, index );
234 private void search(final IContainer rootContainer, final RefactoringStatus status) {
236 IResource[] members = rootContainer.members();
237 for (int i = 0; i < members.length; i++) {
238 if (members[i] instanceof IContainer) {
239 search((IContainer) members[i], status);
241 IFile file = (IFile) members[i];
242 handleFile(file, status);
245 } catch (final CoreException cex) {
246 status.addFatalError(cex.getMessage());
250 private void handleFile(final IFile file, final RefactoringStatus status) {
251 if (PHPFileUtil.isPHPFile(file)) {
252 determineKeyOffsets(file, status);
253 // if (keyOffsets.size() > 0) {
254 // Integer offset = new Integer(keyOffsets);
255 // phpFiles.put(file, offset);
260 protected List getKeyOffsets(final IFile file) {
261 return (List) phpFiles.get(file);
264 // finds the offsets of the identifier to rename
265 // usually, this would be the job of a proper parser;
266 // using a primitive brute-force approach here
267 private void determineKeyOffsets(final IFile file, final RefactoringStatus status) {
268 ArrayList matches = new ArrayList();
270 String content = readFileContent(file, status);
271 Scanner scanner = new Scanner(true, false);
272 scanner.setSource(content.toCharArray());
273 scanner.setPHPMode(false);
274 char[] word = info.getOldName().toCharArray();
276 int fToken = ITerminalSymbols.TokenNameEOF;
278 fToken = scanner.getNextToken();
279 while (fToken != ITerminalSymbols.TokenNameEOF) {
280 if (fToken == ITerminalSymbols.TokenNameVariable || fToken == ITerminalSymbols.TokenNameIdentifier) {
281 if (scanner.equalsCurrentTokenSource(word)) {
282 matches.add(Integer.valueOf(scanner.getCurrentTokenStartPosition()));
285 fToken = scanner.getNextToken();
288 } catch (InvalidInputException e) {
289 String msg = "File: " + file.getLocation().toOSString() + " InvalidInputException " + e.getMessage();
290 status.addError(msg);
291 } catch (SyntaxError e) {
292 String msg = "File: " + file.getLocation().toOSString() + " SyntaxError " + e.getMessage();
293 status.addError(msg);
296 } catch (Exception e) {
297 String msg = "File: " + file.getLocation().toOSString() + " Exception " + e.getMessage();
298 status.addError(msg);
300 if (matches.size() > 0) {
301 phpFiles.put(file, matches);
305 // int candidateIndex = content.indexOf(info.getOldName());
306 // result = candidateIndex;
307 // while( result == -1 && candidateIndex != -1 ) {
308 // if( isKeyOccurrence( content, candidateIndex ) ) {
309 // result = candidateIndex;
312 // if( result == -1 ) {
313 // // still nothing found, we add a warning to the status
314 // // (we have checked the file contains the property, so that we can't
315 // // determine it's offset is probably because of the rough way employed
316 // // here to find it)
317 // String msg = CoreTexts.renamePropertyDelegate_propNotFound
318 // + file.getLocation().toOSString();
319 // status.addWarning( msg );
324 private String readFileContent(final IFile file, final RefactoringStatus refStatus) {
325 String result = null;
327 InputStream is = file.getContents();
328 byte[] buf = new byte[1024];
329 ByteArrayOutputStream bos = new ByteArrayOutputStream();
330 int len = is.read(buf);
332 bos.write(buf, 0, len);
336 result = new String(bos.toByteArray());
337 } catch (Exception ex) {
338 String msg = ex.toString();
339 refStatus.addFatalError(msg);
340 String pluginId = PHPeclipsePlugin.getPluginId();
341 IStatus status = new Status(IStatus.ERROR, pluginId, 0, msg, ex);
342 PHPeclipsePlugin.getDefault().getLog().log(status);
347 // we check only that there is a separator before the next line break (this
348 // is not sufficient, the whole thing may be in a comment etc. ...)
349 // private boolean isKeyOccurrence( final String content,
350 // final int candidateIndex ) {
351 // int index = candidateIndex + info.getOldName().length();
352 // // skip whitespace
353 // while( content.charAt( index ) == ' ' || content.charAt( index ) == '\t' )
357 // return content.charAt( index ) == '=' || content.charAt( index ) == ':';