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