2e14cd82c882c87235a9153db2f6dc8792329a9d
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / ltk / core / RenameIdentifierDelegate.java
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;
5
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;
12 import java.util.Map;
13
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;
20
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;
39
40 /**
41  * <p>
42  * delegate object that contains the logic used by the processor.
43  * </p>
44  *
45  */
46 public class RenameIdentifierDelegate {
47
48         // private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$
49
50         protected final RenameIdentifierInfo info;
51
52         // PHP file with the identifier to rename -> offset of the key
53         protected final Map phpFiles;
54
55         public RenameIdentifierDelegate(final RenameIdentifierInfo info) {
56                 this.info = info;
57                 phpFiles = new HashMap();
58         }
59
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);
70                 }
71                 return result;
72         }
73
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
79                 // base name
80                 IContainer rootContainer;
81                 ArrayList phpProjects = new ArrayList();
82                 IProject project;
83                 if (info.isAllProjects()) {
84                         rootContainer = ResourcesPlugin.getWorkspace().getRoot();
85                         IResource[] members;
86                         try {
87                                 members = rootContainer.members();
88                                 for (int i = 0; i < members.length; i++) {
89                                         if (members[i] instanceof IProject) {
90                                                 project = (IProject) members[i];
91                                                 try {
92                                                         if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
93                                                                 search(project, result);
94                                                         }
95                                                 } catch (CoreException e) {
96                                                         String msg = "Project: " + project.getLocation().toOSString() + " CoreException " + e.getMessage();
97                                                         result.addError(msg);
98                                                 } catch (Exception e) {
99                                                         String msg = "Project: " + project.getLocation().toOSString() + " Exception " + e.getMessage();
100                                                         result.addError(msg);
101                                                 }
102                                         }
103                                 }
104                         } catch (CoreException e) {
105                                 String msg = "Workspace: " + rootContainer.getLocation().toOSString() + " CoreException " + e.getMessage();
106                                 result.addError(msg);
107                         }
108                 } else {
109                         project = info.getSourceFile().getProject();
110                         try {
111                                 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
112                                         search(project, result);
113                                 }
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);
120                         }
121                 }
122
123                 pm.worked(50);
124
125                 if (ctxt != null) {
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);
131                 }
132                 pm.done();
133                 return result;
134         }
135
136         protected void createChange(final IProgressMonitor pm, final CompositeChange rootChange) {
137                 try {
138                         pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges, 100);
139                         // all files in the same bundle
140                         if (info.isUpdateProject()) {
141                                 rootChange.addAll(createChangesForContainer(pm));
142                         }
143                 } finally {
144                         pm.done();
145                 }
146         }
147
148         // helping methods
149         // ////////////////
150
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);
159         //
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);
164         // return result;
165         // }
166
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;
172                 int work = 0;
173                 int indx = 0;
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);
181
182                                 // edit object for the text replacement in the file, there could be
183                                 // multiple childs
184                                 ReplaceEdit edit;
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);
188                                 }
189                                 result.add(tfc);
190                         }
191                         work = Double.valueOf(++indx * percent).intValue();
192                         pm.worked(work);
193                 }
194                 return (Change[]) result.toArray(new Change[result.size()]);
195         }
196
197         protected boolean isEmpty(final String candidate) {
198                 return candidate == null || candidate.trim().length() == 0;
199         }
200
201         // private boolean isPropertyKey( final IFile file, final String candidate ) {
202         // boolean result = false;
203         // try {
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();
210         // }
211         // return result;
212         // }
213
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() );
220         // }
221
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 );
227         // } else {
228         // int index = result.indexOf( EXT_PROPERTIES ) - 1;
229         // result = result.substring( 0, index );
230         // }
231         // return result;
232         // }
233
234         private void search(final IContainer rootContainer, final RefactoringStatus status) {
235                 try {
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);
240                                 } else {
241                                         IFile file = (IFile) members[i];
242                                         handleFile(file, status);
243                                 }
244                         }
245                 } catch (final CoreException cex) {
246                         status.addFatalError(cex.getMessage());
247                 }
248         }
249
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);
256                         // }
257                 }
258         }
259
260         protected List getKeyOffsets(final IFile file) {
261                 return (List) phpFiles.get(file);
262         }
263
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();
269                 try {
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();
275
276                         int fToken = ITerminalSymbols.TokenNameEOF;
277                         try {
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()));
283                                                 }
284                                         }
285                                         fToken = scanner.getNextToken();
286                                 }
287
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);
294                         }
295
296                 } catch (Exception e) {
297                         String msg = "File: " + file.getLocation().toOSString() + " Exception " + e.getMessage();
298                         status.addError(msg);
299                 }
300                 if (matches.size() > 0) {
301                         phpFiles.put(file, matches);
302                 }
303
304                 // int result = -1;
305                 // int candidateIndex = content.indexOf(info.getOldName());
306                 // result = candidateIndex;
307                 // while( result == -1 && candidateIndex != -1 ) {
308                 // if( isKeyOccurrence( content, candidateIndex ) ) {
309                 // result = candidateIndex;
310                 // }
311                 // }
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 );
320                 // }
321                 // return result;
322         }
323
324         private String readFileContent(final IFile file, final RefactoringStatus refStatus) {
325                 String result = null;
326                 try {
327                         InputStream is = file.getContents();
328                         byte[] buf = new byte[1024];
329                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
330                         int len = is.read(buf);
331                         while (len > 0) {
332                                 bos.write(buf, 0, len);
333                                 len = is.read(buf);
334                         }
335                         is.close();
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);
343                 }
344                 return result;
345         }
346
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' )
354         // {
355         // index++;
356         // }
357         // return content.charAt( index ) == '=' || content.charAt( index ) == ':';
358         // }
359 }