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;
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;
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.core.util.PHPFileUtil;
19 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
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;
42 * delegate object that contains the logic used by the processor.
46 public class RenameIdentifierDelegate {
48 // private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$
50 protected final RenameIdentifierInfo info;
52 // PHP file with the identifier to rename -> offset of the key
53 protected final Map phpFiles;
55 public RenameIdentifierDelegate(final RenameIdentifierInfo info) {
57 phpFiles = new HashMap();
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);
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
81 IContainer rootContainer;
83 if (info.isAllProjects()) {
84 rootContainer = ResourcesPlugin.getWorkspace().getRoot();
87 members = rootContainer.members();
88 for (int i = 0; i < members.length; i++) {
89 if (members[i] instanceof IProject) {
90 project = (IProject) members[i];
93 .isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
94 search(project, result);
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);
109 } catch (CoreException e) {
110 String msg = "Workspace: "
111 + rootContainer.getFullPath().toOSString()
112 + " CoreException " + e.getMessage();
113 result.addError(msg);
116 project = info.getSourceFile().getProject();
118 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
119 search(project, result);
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);
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);
146 protected void createChange(final IProgressMonitor pm,
147 final CompositeChange rootChange) {
149 pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges,
151 // all files in the same bundle
152 if (info.isUpdateProject()) {
153 rootChange.addAll(createChangesForContainer(pm));
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);
172 // // edit object for the text replacement in the file, this is the only
174 // ReplaceEdit edit = new ReplaceEdit(info.getOffset(),
175 // info.getOldName().length(), info.getNewName());
176 // fileChangeRootEdit.addChild(edit);
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;
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);
195 // edit object for the text replacement in the file, there could
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);
206 work = new Double((++indx) * percent).intValue();
209 return (Change[]) result.toArray(new Change[result.size()]);
212 protected boolean isEmpty(final String candidate) {
213 return candidate == null || candidate.trim().length() == 0;
216 // private boolean isPropertyKey( final IFile file, final String candidate )
218 // boolean result = false;
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();
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() );
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 );
244 // int index = result.indexOf( EXT_PROPERTIES ) - 1;
245 // result = result.substring( 0, index );
250 private void search(final IContainer rootContainer,
251 final RefactoringStatus status) {
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);
258 IFile file = (IFile) members[i];
259 handleFile(file, status);
262 } catch (final CoreException cex) {
263 status.addFatalError(cex.getMessage());
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);
277 protected List getKeyOffsets(final IFile file) {
278 return (List) phpFiles.get(file);
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();
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();
294 int fToken = ITerminalSymbols.TokenNameEOF;
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()));
305 fToken = scanner.getNextToken();
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);
318 } catch (Exception e) {
319 String msg = "File: " + file.getFullPath().toOSString()
320 + " Exception " + e.getMessage();
321 status.addError(msg);
323 if (matches.size() > 0) {
324 phpFiles.put(file, matches);
328 // int candidateIndex = content.indexOf(info.getOldName());
329 // result = candidateIndex;
330 // while( result == -1 && candidateIndex != -1 ) {
331 // if( isKeyOccurrence( content, candidateIndex ) ) {
332 // result = candidateIndex;
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
340 // // here to find it)
341 // String msg = CoreTexts.renamePropertyDelegate_propNotFound
342 // + file.getLocation().toOSString();
343 // status.addWarning( msg );
348 private String readFileContent(final IFile file,
349 final RefactoringStatus refStatus) {
350 String result = null;
352 InputStream is = file.getContents();
353 byte[] buf = new byte[1024];
354 ByteArrayOutputStream bos = new ByteArrayOutputStream();
355 int len = is.read(buf);
357 bos.write(buf, 0, len);
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);
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'
383 // return content.charAt( index ) == '=' || content.charAt( index ) == ':';