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