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.ui.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;
82 ArrayList phpProjects = new ArrayList();
84 if (info.isAllProjects()) {
85 rootContainer = ResourcesPlugin.getWorkspace().getRoot();
88 members = rootContainer.members();
89 for (int i = 0; i < members.length; i++) {
90 if (members[i] instanceof IProject) {
91 project = (IProject) members[i];
94 .isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
95 search(project, result);
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);
110 } catch (CoreException e) {
111 String msg = "Workspace: "
112 + rootContainer.getLocation().toOSString()
113 + " CoreException " + e.getMessage();
114 result.addError(msg);
117 project = info.getSourceFile().getProject();
119 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
120 search(project, result);
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);
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);
147 protected void createChange(final IProgressMonitor pm,
148 final CompositeChange rootChange) {
150 pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges,
152 // all files in the same bundle
153 if (info.isUpdateProject()) {
154 rootChange.addAll(createChangesForContainer(pm));
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);
173 // // edit object for the text replacement in the file, this is the only
175 // ReplaceEdit edit = new ReplaceEdit(info.getOffset(),
176 // info.getOldName().length(), info.getNewName());
177 // fileChangeRootEdit.addChild(edit);
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;
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);
196 // edit object for the text replacement in the file, there could
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);
207 work = new Double((++indx) * percent).intValue();
210 return (Change[]) result.toArray(new Change[result.size()]);
213 protected boolean isEmpty(final String candidate) {
214 return candidate == null || candidate.trim().length() == 0;
217 // private boolean isPropertyKey( final IFile file, final String candidate )
219 // boolean result = false;
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();
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() );
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 );
245 // int index = result.indexOf( EXT_PROPERTIES ) - 1;
246 // result = result.substring( 0, index );
251 private void search(final IContainer rootContainer,
252 final RefactoringStatus status) {
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);
259 IFile file = (IFile) members[i];
260 handleFile(file, status);
263 } catch (final CoreException cex) {
264 status.addFatalError(cex.getMessage());
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);
278 protected List getKeyOffsets(final IFile file) {
279 return (List) phpFiles.get(file);
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();
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();
295 int fToken = ITerminalSymbols.TokenNameEOF;
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()));
306 fToken = scanner.getNextToken();
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);
319 } catch (Exception e) {
320 String msg = "File: " + file.getLocation().toOSString()
321 + " Exception " + e.getMessage();
322 status.addError(msg);
324 if (matches.size() > 0) {
325 phpFiles.put(file, matches);
329 // int candidateIndex = content.indexOf(info.getOldName());
330 // result = candidateIndex;
331 // while( result == -1 && candidateIndex != -1 ) {
332 // if( isKeyOccurrence( content, candidateIndex ) ) {
333 // result = candidateIndex;
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
341 // // here to find it)
342 // String msg = CoreTexts.renamePropertyDelegate_propNotFound
343 // + file.getLocation().toOSString();
344 // status.addWarning( msg );
349 private String readFileContent(final IFile file,
350 final RefactoringStatus refStatus) {
351 String result = null;
353 InputStream is = file.getContents();
354 byte[] buf = new byte[1024];
355 ByteArrayOutputStream bos = new ByteArrayOutputStream();
356 int len = is.read(buf);
358 bos.write(buf, 0, len);
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);
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'
384 // return content.charAt( index ) == '=' || content.charAt( index ) == ':';