1 // Copyright (c) 2005 by Leif Frenzel. All rights reserved.
2 // See http://leiffrenzel.de
3 package net.sourceforge.phpdt.ltk.core;
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;
13 import net.sourceforge.phpdt.internal.ui.util.PHPFileUtil;
14 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
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;
36 * delegate object that contains the logic used by the processor.
39 * @author Leif Frenzel
41 class RenamePropertyDelegate {
43 // private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$
45 private final RenamePropertyInfo info;
47 // properties file with the key to rename -> offset of the key
48 private final Map phpFiles;
50 RenamePropertyDelegate(final RenamePropertyInfo info) {
52 phpFiles = new HashMap();
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);
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
75 IContainer rootContainer;
76 if (info.isAllProjects()) {
77 rootContainer = ResourcesPlugin.getWorkspace().getRoot();
79 rootContainer = info.getSourceFile().getProject();
81 search(rootContainer, result);
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);
95 void createChange(final IProgressMonitor pm, final CompositeChange rootChange) {
97 pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges, 100);
98 // the property which was directly selected by the user
99 rootChange.add(createRenameChange());
101 // all files in the same bundle
102 if (info.isUpdateBundle()) {
103 rootChange.addAll(createChangesForBundle());
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);
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);
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();
135 TextFileChange tfc = new TextFileChange(file.getName(), file);
136 MultiTextEdit fileChangeRootEdit = new MultiTextEdit();
137 tfc.setEdit(fileChangeRootEdit);
139 // edit object for the text replacement in the file, this is the only
141 ReplaceEdit edit = new ReplaceEdit(getKeyOffset(file), info.getOldName().length(), info.getNewName());
142 fileChangeRootEdit.addChild(edit);
146 return (Change[]) result.toArray(new Change[result.size()]);
149 private boolean isEmpty(final String candidate) {
150 return candidate == null || candidate.trim().length() == 0;
153 // private boolean isPropertyKey( final IFile file, final String candidate ) {
154 // boolean result = false;
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();
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() );
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 );
180 // int index = result.indexOf( EXT_PROPERTIES ) - 1;
181 // result = result.substring( 0, index );
186 private void search(final IContainer rootContainer, final RefactoringStatus status) {
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);
193 IFile file = (IFile) members[i];
194 handleFile(file, status);
197 } catch (final CoreException cex) {
198 status.addFatalError(cex.getMessage());
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);
212 private int getKeyOffset(final IFile file) {
213 return ((Integer) phpFiles.get(file)).intValue();
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);
222 while ((indx = content.indexOf(info.getOldName(), indx)) >= 0) {
223 phpFiles.put(file, Integer.valueOf(indx++));
226 // int candidateIndex = content.indexOf(info.getOldName());
227 // result = candidateIndex;
228 // while( result == -1 && candidateIndex != -1 ) {
229 // if( isKeyOccurrence( content, candidateIndex ) ) {
230 // result = candidateIndex;
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 );
245 private String readFileContent(final IFile file, final RefactoringStatus refStatus) {
246 String result = null;
248 InputStream is = file.getContents();
249 byte[] buf = new byte[1024];
250 ByteArrayOutputStream bos = new ByteArrayOutputStream();
251 int len = is.read(buf);
253 bos.write(buf, 0, len);
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);
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' )
278 // return content.charAt( index ) == '=' || content.charAt( index ) == ':';