Basic Reafctoring functionality adapted from Leif Frenzels sources in eclipse-magazin...
[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 package net.sourceforge.phpdt.ltk.core;
4
5 import java.io.ByteArrayOutputStream;
6 import java.io.InputStream;
7 import java.util.ArrayList;
8 import java.util.HashMap;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Map;
12
13 import net.sourceforge.phpdt.internal.ui.util.PHPFileUtil;
14 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
15
16 import org.eclipse.core.resources.IContainer;
17 import org.eclipse.core.resources.IFile;
18 import org.eclipse.core.resources.IResource;
19 import org.eclipse.core.resources.ResourcesPlugin;
20 import org.eclipse.core.runtime.CoreException;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.IStatus;
23 import org.eclipse.core.runtime.Status;
24 import org.eclipse.ltk.core.refactoring.Change;
25 import org.eclipse.ltk.core.refactoring.CompositeChange;
26 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
27 import org.eclipse.ltk.core.refactoring.TextFileChange;
28 import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
29 import org.eclipse.ltk.core.refactoring.participants.IConditionChecker;
30 import org.eclipse.ltk.core.refactoring.participants.ValidateEditChecker;
31 import org.eclipse.text.edits.MultiTextEdit;
32 import org.eclipse.text.edits.ReplaceEdit;
33
34 /**
35  * <p>
36  * delegate object that contains the logic used by the processor.
37  * </p>
38  *
39  * @author Leif Frenzel
40  */
41 class RenamePropertyDelegate {
42
43         // private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$
44
45         private final RenamePropertyInfo info;
46
47         // properties file with the key to rename -> offset of the key
48         private final Map phpFiles;
49
50         RenamePropertyDelegate(final RenamePropertyInfo info) {
51                 this.info = info;
52                 phpFiles = new HashMap();
53         }
54
55         RefactoringStatus checkInitialConditions() {
56                 RefactoringStatus result = new RefactoringStatus();
57                 IFile sourceFile = info.getSourceFile();
58                 if (sourceFile == null || !sourceFile.exists()) {
59                         result.addFatalError(CoreTexts.renamePropertyDelegate_noSourceFile);
60                 } else if (info.getSourceFile().isReadOnly()) {
61                         result.addFatalError(CoreTexts.renamePropertyDelegate_roFile);
62                 } else if (isEmpty(info.getOldName())) {
63                         // || !isPropertyKey( info.getSourceFile(), info.getOldName() ) ) {
64                         result.addFatalError(CoreTexts.renamePropertyDelegate_noPHPKey);
65                 }
66                 return result;
67         }
68
69         RefactoringStatus checkFinalConditions(final IProgressMonitor pm, final CheckConditionsContext ctxt) {
70                 RefactoringStatus result = new RefactoringStatus();
71                 pm.beginTask(CoreTexts.renamePropertyDelegate_checking, 100);
72                 // do something long-running here: traverse the entire project (or even
73                 // workspace) to look for all *.properties files with the same bundle
74                 // base name
75                 IContainer rootContainer;
76                 if (info.isAllProjects()) {
77                         rootContainer = ResourcesPlugin.getWorkspace().getRoot();
78                 } else {
79                         rootContainer = info.getSourceFile().getProject();
80                 }
81                 search(rootContainer, result);
82                 pm.worked(50);
83
84                 if (ctxt != null) {
85                         IFile[] files = new IFile[phpFiles.size()];
86                         phpFiles.keySet().toArray(files);
87                         IConditionChecker checker = ctxt.getChecker(ValidateEditChecker.class);
88                         ValidateEditChecker editChecker = (ValidateEditChecker) checker;
89                         editChecker.addFiles(files);
90                 }
91                 pm.done();
92                 return result;
93         }
94
95         void createChange(final IProgressMonitor pm, final CompositeChange rootChange) {
96                 try {
97                         pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges, 100);
98                         // the property which was directly selected by the user
99                         rootChange.add(createRenameChange());
100                         pm.worked(10);
101                         // all files in the same bundle
102                         if (info.isUpdateBundle()) {
103                                 rootChange.addAll(createChangesForBundle());
104                         }
105                         pm.worked(90);
106                 } finally {
107                         pm.done();
108                 }
109         }
110
111         // helping methods
112         // ////////////////
113
114         private Change createRenameChange() {
115                 // create a change object for the file that contains the property the
116                 // user has selected to rename
117                 IFile file = info.getSourceFile();
118                 TextFileChange result = new TextFileChange(file.getName(), file);
119                 // a file change contains a tree of edits, first add the root of them
120                 MultiTextEdit fileChangeRootEdit = new MultiTextEdit();
121                 result.setEdit(fileChangeRootEdit);
122
123                 // edit object for the text replacement in the file, this is the only child
124                 ReplaceEdit edit = new ReplaceEdit(info.getOffset(), info.getOldName().length(), info.getNewName());
125                 fileChangeRootEdit.addChild(edit);
126                 return result;
127         }
128
129         private Change[] createChangesForBundle() {
130                 List result = new ArrayList();
131                 Iterator it = phpFiles.keySet().iterator();
132                 while (it.hasNext()) {
133                         IFile file = (IFile) it.next();
134
135                         TextFileChange tfc = new TextFileChange(file.getName(), file);
136                         MultiTextEdit fileChangeRootEdit = new MultiTextEdit();
137                         tfc.setEdit(fileChangeRootEdit);
138
139                         // edit object for the text replacement in the file, this is the only
140                         // child
141                         ReplaceEdit edit = new ReplaceEdit(getKeyOffset(file), info.getOldName().length(), info.getNewName());
142                         fileChangeRootEdit.addChild(edit);
143
144                         result.add(tfc);
145                 }
146                 return (Change[]) result.toArray(new Change[result.size()]);
147         }
148
149         private boolean isEmpty(final String candidate) {
150                 return candidate == null || candidate.trim().length() == 0;
151         }
152
153         // private boolean isPropertyKey( final IFile file, final String candidate ) {
154         // boolean result = false;
155         // try {
156         // Properties props = new Properties();
157         // props.load( file.getContents() );
158         // result = props.containsKey( candidate );
159         // } catch( Exception ex ) {
160         // // ignore this, we just assume this is not a favourable situation
161         // ex.printStackTrace();
162         // }
163         // return result;
164         // }
165
166         // whether the file is a PHP file with the same base name as the
167         // one we refactor and contains the key that interests us
168         private boolean isToRefactor(final IFile file) {
169                 return PHPFileUtil.isPHPFile(file);
170                 // && !file.equals( info.getSourceFile() )
171                 // && isPropertyKey( file, info.getOldName() );
172         }
173
174         // private String getBundleBaseName() {
175         // String result = info.getSourceFile().getName();
176         // int underscoreIndex = result.indexOf( '_' );
177         // if( underscoreIndex != -1 ) {
178         // result = result.substring( 0, underscoreIndex );
179         // } else {
180         // int index = result.indexOf( EXT_PROPERTIES ) - 1;
181         // result = result.substring( 0, index );
182         // }
183         // return result;
184         // }
185
186         private void search(final IContainer rootContainer, final RefactoringStatus status) {
187                 try {
188                         IResource[] members = rootContainer.members();
189                         for (int i = 0; i < members.length; i++) {
190                                 if (members[i] instanceof IContainer) {
191                                         search((IContainer) members[i], status);
192                                 } else {
193                                         IFile file = (IFile) members[i];
194                                         handleFile(file, status);
195                                 }
196                         }
197                 } catch (final CoreException cex) {
198                         status.addFatalError(cex.getMessage());
199                 }
200         }
201
202         private void handleFile(final IFile file, final RefactoringStatus status) {
203                 if (isToRefactor(file)) {
204                         determineKeyOffsets(file, status);
205                         // if (keyOffsets.size() > 0) {
206                         // Integer offset = new Integer(keyOffsets);
207                         // phpFiles.put(file, offset);
208                         // }
209                 }
210         }
211
212         private int getKeyOffset(final IFile file) {
213                 return ((Integer) phpFiles.get(file)).intValue();
214         }
215
216         // finds the offset of the property key to rename
217         // usually, this would be the job of a proper parser;
218         // using a primitive brute-force approach here
219         private void determineKeyOffsets(final IFile file, final RefactoringStatus status) {
220                 String content = readFileContent(file, status);
221                 int indx = 0;
222                 while ((indx = content.indexOf(info.getOldName(), indx)) >= 0) {
223                         phpFiles.put(file, Integer.valueOf(indx++));
224                 }
225                 // int result = -1;
226                 // int candidateIndex = content.indexOf(info.getOldName());
227                 // result = candidateIndex;
228                 // while( result == -1 && candidateIndex != -1 ) {
229                 // if( isKeyOccurrence( content, candidateIndex ) ) {
230                 // result = candidateIndex;
231                 // }
232                 // }
233                 // if( result == -1 ) {
234                 // // still nothing found, we add a warning to the status
235                 // // (we have checked the file contains the property, so that we can't
236                 // // determine it's offset is probably because of the rough way employed
237                 // // here to find it)
238                 // String msg = CoreTexts.renamePropertyDelegate_propNotFound
239                 // + file.getLocation().toOSString();
240                 // status.addWarning( msg );
241                 // }
242                 // return result;
243         }
244
245         private String readFileContent(final IFile file, final RefactoringStatus refStatus) {
246                 String result = null;
247                 try {
248                         InputStream is = file.getContents();
249                         byte[] buf = new byte[1024];
250                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
251                         int len = is.read(buf);
252                         while (len > 0) {
253                                 bos.write(buf, 0, len);
254                                 len = is.read(buf);
255                         }
256                         is.close();
257                         result = new String(bos.toByteArray());
258                 } catch (Exception ex) {
259                         String msg = ex.toString();
260                         refStatus.addFatalError(msg);
261                         String pluginId = PHPeclipsePlugin.getPluginId();
262                         IStatus status = new Status(IStatus.ERROR, pluginId, 0, msg, ex);
263                         PHPeclipsePlugin.getDefault().getLog().log(status);
264                 }
265                 return result;
266         }
267
268         // we check only that there is a separator before the next line break (this
269         // is not sufficient, the whole thing may be in a comment etc. ...)
270         // private boolean isKeyOccurrence( final String content,
271         // final int candidateIndex ) {
272         // int index = candidateIndex + info.getOldName().length();
273         // // skip whitespace
274         // while( content.charAt( index ) == ' ' || content.charAt( index ) == '\t' )
275         // {
276         // index++;
277         // }
278         // return content.charAt( index ) == '=' || content.charAt( index ) == ':';
279         // }
280 }