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.
45 * @author Leif Frenzel
47 class RenamePropertyDelegate {
49 // private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$
51 private final RenamePropertyInfo info;
53 // PHP file with the identifier to rename -> offset of the key
54 private final Map phpFiles;
56 RenamePropertyDelegate(final RenamePropertyInfo info) {
58 phpFiles = new HashMap();
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);
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
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];
93 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
94 search(project, result);
96 } catch (CoreException e) {
97 String msg = "Project: " + project.getLocation().toOSString() + " CoreException " + e.getMessage();
99 } catch (Exception e) {
100 String msg = "Project: " + project.getLocation().toOSString() + " Exception " + e.getMessage();
101 result.addError(msg);
105 } catch (CoreException e) {
106 String msg = "Workspace: " + rootContainer.getLocation().toOSString() + " CoreException " + e.getMessage();
107 result.addError(msg);
110 project = info.getSourceFile().getProject();
112 if (project.isNatureEnabled(PHPeclipsePlugin.PHP_NATURE_ID)) {
113 search(project, result);
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);
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);
137 void createChange(final IProgressMonitor pm, final CompositeChange rootChange) {
139 pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges, 100);
140 // all files in the same bundle
141 if (info.isUpdateProject()) {
142 rootChange.addAll(createChangesForContainer(pm));
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);
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);
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;
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);
183 // edit object for the text replacement in the file, there could be
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);
192 work = Double.valueOf(++indx * percent).intValue();
195 return (Change[]) result.toArray(new Change[result.size()]);
198 private boolean isEmpty(final String candidate) {
199 return candidate == null || candidate.trim().length() == 0;
202 // private boolean isPropertyKey( final IFile file, final String candidate ) {
203 // boolean result = false;
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();
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() );
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 );
229 // int index = result.indexOf( EXT_PROPERTIES ) - 1;
230 // result = result.substring( 0, index );
235 private void search(final IContainer rootContainer, final RefactoringStatus status) {
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);
242 IFile file = (IFile) members[i];
243 handleFile(file, status);
246 } catch (final CoreException cex) {
247 status.addFatalError(cex.getMessage());
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);
261 private List getKeyOffsets(final IFile file) {
262 return (List) phpFiles.get(file);
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();
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();
277 int fToken = ITerminalSymbols.TokenNameEOF;
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()));
286 fToken = scanner.getNextToken();
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);
297 } catch (Exception e) {
298 String msg = "File: " + file.getLocation().toOSString() + " Exception " + e.getMessage();
299 status.addError(msg);
301 if (matches.size() > 0) {
302 phpFiles.put(file, matches);
306 // int candidateIndex = content.indexOf(info.getOldName());
307 // result = candidateIndex;
308 // while( result == -1 && candidateIndex != -1 ) {
309 // if( isKeyOccurrence( content, candidateIndex ) ) {
310 // result = candidateIndex;
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 );
325 private String readFileContent(final IFile file, final RefactoringStatus refStatus) {
326 String result = null;
328 InputStream is = file.getContents();
329 byte[] buf = new byte[1024];
330 ByteArrayOutputStream bos = new ByteArrayOutputStream();
331 int len = is.read(buf);
333 bos.write(buf, 0, len);
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);
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' )
358 // return content.charAt( index ) == '=' || content.charAt( index ) == ':';