beaf423c081a3cb8c6056240741dd2afd39a1d54
[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,
75                         final CheckConditionsContext ctxt) {
76                 RefactoringStatus result = new RefactoringStatus();
77                 pm.beginTask(CoreTexts.renamePropertyDelegate_checking, 100);
78                 // do something long-running here: traverse the entire project (or even
79                 // workspace) to look for all *.properties files with the same bundle
80                 // base name
81                 IContainer rootContainer;
82                 ArrayList phpProjects = new ArrayList();
83                 IProject project;
84                 if (info.isAllProjects()) {
85                         rootContainer = ResourcesPlugin.getWorkspace().getRoot();
86                         IResource[] members;
87                         try {
88                                 members = rootContainer.members();
89                                 for (int i = 0; i < members.length; i++) {
90                                         if (members[i] instanceof IProject) {
91                                                 project = (IProject) members[i];
92                                                 try {
93                                                         if (project
94                                                                         .isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
95                                                                 search(project, result);
96                                                         }
97                                                 } catch (CoreException e) {
98                                                         String msg = "Project: "
99                                                                         + project.getLocation().toOSString()
100                                                                         + " CoreException " + e.getMessage();
101                                                         result.addError(msg);
102                                                 } catch (Exception e) {
103                                                         String msg = "Project: "
104                                                                         + project.getLocation().toOSString()
105                                                                         + " Exception " + e.getMessage();
106                                                         result.addError(msg);
107                                                 }
108                                         }
109                                 }
110                         } catch (CoreException e) {
111                                 String msg = "Workspace: "
112                                                 + rootContainer.getLocation().toOSString()
113                                                 + " CoreException " + e.getMessage();
114                                 result.addError(msg);
115                         }
116                 } else {
117                         project = info.getSourceFile().getProject();
118                         try {
119                                 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
120                                         search(project, result);
121                                 }
122                         } catch (CoreException e) {
123                                 String msg = "Project: " + project.getLocation().toOSString()
124                                                 + " CoreException " + e.getMessage();
125                                 result.addError(msg);
126                         } catch (Exception e) {
127                                 String msg = "Project: " + project.getLocation().toOSString()
128                                                 + " Exception " + e.getMessage();
129                                 result.addError(msg);
130                         }
131                 }
132
133                 pm.worked(50);
134
135                 if (ctxt != null) {
136                         IFile[] files = new IFile[phpFiles.size()];
137                         phpFiles.keySet().toArray(files);
138                         IConditionChecker checker = ctxt
139                                         .getChecker(ValidateEditChecker.class);
140                         ValidateEditChecker editChecker = (ValidateEditChecker) checker;
141                         editChecker.addFiles(files);
142                 }
143                 pm.done();
144                 return result;
145         }
146
147         protected void createChange(final IProgressMonitor pm,
148                         final CompositeChange rootChange) {
149                 try {
150                         pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges,
151                                         100);
152                         // all files in the same bundle
153                         if (info.isUpdateProject()) {
154                                 rootChange.addAll(createChangesForContainer(pm));
155                         }
156                 } finally {
157                         pm.done();
158                 }
159         }
160
161         // helping methods
162         // ////////////////
163
164         // private Change createRenameChange() {
165         // // create a change object for the file that contains the property the
166         // // user has selected to rename
167         // IFile file = info.getSourceFile();
168         // TextFileChange result = new TextFileChange(file.getName(), file);
169         // // a file change contains a tree of edits, first add the root of them
170         // MultiTextEdit fileChangeRootEdit = new MultiTextEdit();
171         // result.setEdit(fileChangeRootEdit);
172         //
173         // // edit object for the text replacement in the file, this is the only
174         // child
175         // ReplaceEdit edit = new ReplaceEdit(info.getOffset(),
176         // info.getOldName().length(), info.getNewName());
177         // fileChangeRootEdit.addChild(edit);
178         // return result;
179         // }
180
181         protected Change[] createChangesForContainer(final IProgressMonitor pm) {
182                 List result = new ArrayList();
183                 Iterator it = phpFiles.keySet().iterator();
184                 int numberOfFiles = phpFiles.size();
185                 double percent = 100 / numberOfFiles;
186                 int work = 0;
187                 int indx = 0;
188                 while (it.hasNext()) {
189                         IFile file = (IFile) it.next();
190                         List list = getKeyOffsets(file);
191                         if (list != null && list.size() > 0) {
192                                 TextFileChange tfc = new TextFileChange(file.getName(), file);
193                                 MultiTextEdit fileChangeRootEdit = new MultiTextEdit();
194                                 tfc.setEdit(fileChangeRootEdit);
195
196                                 // edit object for the text replacement in the file, there could
197                                 // be
198                                 // multiple childs
199                                 ReplaceEdit edit;
200                                 for (int i = 0; i < list.size(); i++) {
201                                         edit = new ReplaceEdit(((Integer) list.get(i)).intValue(),
202                                                         info.getOldName().length(), info.getNewName());
203                                         fileChangeRootEdit.addChild(edit);
204                                 }
205                                 result.add(tfc);
206                         }
207                         work = new Double((++indx) * percent).intValue();
208                         pm.worked(work);
209                 }
210                 return (Change[]) result.toArray(new Change[result.size()]);
211         }
212
213         protected boolean isEmpty(final String candidate) {
214                 return candidate == null || candidate.trim().length() == 0;
215         }
216
217         // private boolean isPropertyKey( final IFile file, final String candidate )
218         // {
219         // boolean result = false;
220         // try {
221         // Properties props = new Properties();
222         // props.load( file.getContents() );
223         // result = props.containsKey( candidate );
224         // } catch( Exception ex ) {
225         // // ignore this, we just assume this is not a favourable situation
226         // ex.printStackTrace();
227         // }
228         // return result;
229         // }
230
231         // // whether the file is a PHP file with the same base name as the
232         // // one we refactor and contains the key that interests us
233         // private boolean isToRefactor(final IFile file) {
234         // return PHPFileUtil.isPHPFile(file);
235         // // && !file.equals( info.getSourceFile() )
236         // // && isPropertyKey( file, info.getOldName() );
237         // }
238
239         // private String getBundleBaseName() {
240         // String result = info.getSourceFile().getName();
241         // int underscoreIndex = result.indexOf( '_' );
242         // if( underscoreIndex != -1 ) {
243         // result = result.substring( 0, underscoreIndex );
244         // } else {
245         // int index = result.indexOf( EXT_PROPERTIES ) - 1;
246         // result = result.substring( 0, index );
247         // }
248         // return result;
249         // }
250
251         private void search(final IContainer rootContainer,
252                         final RefactoringStatus status) {
253                 try {
254                         IResource[] members = rootContainer.members();
255                         for (int i = 0; i < members.length; i++) {
256                                 if (members[i] instanceof IContainer) {
257                                         search((IContainer) members[i], status);
258                                 } else {
259                                         IFile file = (IFile) members[i];
260                                         handleFile(file, status);
261                                 }
262                         }
263                 } catch (final CoreException cex) {
264                         status.addFatalError(cex.getMessage());
265                 }
266         }
267
268         private void handleFile(final IFile file, final RefactoringStatus status) {
269                 if (PHPFileUtil.isPHPFile(file)) {
270                         determineKeyOffsets(file, status);
271                         // if (keyOffsets.size() > 0) {
272                         // Integer offset = new Integer(keyOffsets);
273                         // phpFiles.put(file, offset);
274                         // }
275                 }
276         }
277
278         protected List getKeyOffsets(final IFile file) {
279                 return (List) phpFiles.get(file);
280         }
281
282         // finds the offsets of the identifier to rename
283         // usually, this would be the job of a proper parser;
284         // using a primitive brute-force approach here
285         private void determineKeyOffsets(final IFile file,
286                         final RefactoringStatus status) {
287                 ArrayList matches = new ArrayList();
288                 try {
289                         String content = readFileContent(file, status);
290                         Scanner scanner = new Scanner(true, false);
291                         scanner.setSource(content.toCharArray());
292                         scanner.setPHPMode(false);
293                         char[] word = info.getOldName().toCharArray();
294
295                         int fToken = ITerminalSymbols.TokenNameEOF;
296                         try {
297                                 fToken = scanner.getNextToken();
298                                 while (fToken != ITerminalSymbols.TokenNameEOF) {
299                                         if (fToken == ITerminalSymbols.TokenNameVariable
300                                                         || fToken == ITerminalSymbols.TokenNameIdentifier) {
301                                                 if (scanner.equalsCurrentTokenSource(word)) {
302                                                         matches.add(new Integer(scanner
303                                                                         .getCurrentTokenStartPosition()));
304                                                 }
305                                         }
306                                         fToken = scanner.getNextToken();
307                                 }
308
309                         } catch (InvalidInputException e) {
310                                 String msg = "File: " + file.getLocation().toOSString()
311                                                 + " InvalidInputException " + e.getMessage();
312                                 status.addError(msg);
313                         } catch (SyntaxError e) {
314                                 String msg = "File: " + file.getLocation().toOSString()
315                                                 + " SyntaxError " + e.getMessage();
316                                 status.addError(msg);
317                         }
318
319                 } catch (Exception e) {
320                         String msg = "File: " + file.getLocation().toOSString()
321                                         + " Exception " + e.getMessage();
322                         status.addError(msg);
323                 }
324                 if (matches.size() > 0) {
325                         phpFiles.put(file, matches);
326                 }
327
328                 // int result = -1;
329                 // int candidateIndex = content.indexOf(info.getOldName());
330                 // result = candidateIndex;
331                 // while( result == -1 && candidateIndex != -1 ) {
332                 // if( isKeyOccurrence( content, candidateIndex ) ) {
333                 // result = candidateIndex;
334                 // }
335                 // }
336                 // if( result == -1 ) {
337                 // // still nothing found, we add a warning to the status
338                 // // (we have checked the file contains the property, so that we can't
339                 // // determine it's offset is probably because of the rough way
340                 // employed
341                 // // here to find it)
342                 // String msg = CoreTexts.renamePropertyDelegate_propNotFound
343                 // + file.getLocation().toOSString();
344                 // status.addWarning( msg );
345                 // }
346                 // return result;
347         }
348
349         private String readFileContent(final IFile file,
350                         final RefactoringStatus refStatus) {
351                 String result = null;
352                 try {
353                         InputStream is = file.getContents();
354                         byte[] buf = new byte[1024];
355                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
356                         int len = is.read(buf);
357                         while (len > 0) {
358                                 bos.write(buf, 0, len);
359                                 len = is.read(buf);
360                         }
361                         is.close();
362                         result = new String(bos.toByteArray());
363                 } catch (Exception ex) {
364                         String msg = ex.toString();
365                         refStatus.addFatalError(msg);
366                         String pluginId = PHPeclipsePlugin.getPluginId();
367                         IStatus status = new Status(IStatus.ERROR, pluginId, 0, msg, ex);
368                         PHPeclipsePlugin.getDefault().getLog().log(status);
369                 }
370                 return result;
371         }
372
373         // we check only that there is a separator before the next line break (this
374         // is not sufficient, the whole thing may be in a comment etc. ...)
375         // private boolean isKeyOccurrence( final String content,
376         // final int candidateIndex ) {
377         // int index = candidateIndex + info.getOldName().length();
378         // // skip whitespace
379         // while( content.charAt( index ) == ' ' || content.charAt( index ) == '\t'
380         // )
381         // {
382         // index++;
383         // }
384         // return content.charAt( index ) == '=' || content.charAt( index ) == ':';
385         // }
386 }