// Copyright (c) 2005 by Leif Frenzel. All rights reserved. // See http://leiffrenzel.de package net.sourceforge.phpdt.ltk.core; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import net.sourceforge.phpdt.internal.ui.util.PHPFileUtil; import net.sourceforge.phpeclipse.PHPeclipsePlugin; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; import org.eclipse.ltk.core.refactoring.participants.IConditionChecker; import org.eclipse.ltk.core.refactoring.participants.ValidateEditChecker; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; /** *

* delegate object that contains the logic used by the processor. *

* * @author Leif Frenzel */ class RenamePropertyDelegate { // private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$ private final RenamePropertyInfo info; // properties file with the key to rename -> offset of the key private final Map phpFiles; RenamePropertyDelegate(final RenamePropertyInfo info) { this.info = info; phpFiles = new HashMap(); } RefactoringStatus checkInitialConditions() { RefactoringStatus result = new RefactoringStatus(); IFile sourceFile = info.getSourceFile(); if (sourceFile == null || !sourceFile.exists()) { result.addFatalError(CoreTexts.renamePropertyDelegate_noSourceFile); } else if (info.getSourceFile().isReadOnly()) { result.addFatalError(CoreTexts.renamePropertyDelegate_roFile); } else if (isEmpty(info.getOldName())) { // || !isPropertyKey( info.getSourceFile(), info.getOldName() ) ) { result.addFatalError(CoreTexts.renamePropertyDelegate_noPHPKey); } return result; } RefactoringStatus checkFinalConditions(final IProgressMonitor pm, final CheckConditionsContext ctxt) { RefactoringStatus result = new RefactoringStatus(); pm.beginTask(CoreTexts.renamePropertyDelegate_checking, 100); // do something long-running here: traverse the entire project (or even // workspace) to look for all *.properties files with the same bundle // base name IContainer rootContainer; if (info.isAllProjects()) { rootContainer = ResourcesPlugin.getWorkspace().getRoot(); } else { rootContainer = info.getSourceFile().getProject(); } search(rootContainer, result); pm.worked(50); if (ctxt != null) { IFile[] files = new IFile[phpFiles.size()]; phpFiles.keySet().toArray(files); IConditionChecker checker = ctxt.getChecker(ValidateEditChecker.class); ValidateEditChecker editChecker = (ValidateEditChecker) checker; editChecker.addFiles(files); } pm.done(); return result; } void createChange(final IProgressMonitor pm, final CompositeChange rootChange) { try { pm.beginTask(CoreTexts.renamePropertyDelegate_collectingChanges, 100); // the property which was directly selected by the user rootChange.add(createRenameChange()); pm.worked(10); // all files in the same bundle if (info.isUpdateBundle()) { rootChange.addAll(createChangesForBundle()); } pm.worked(90); } finally { pm.done(); } } // helping methods // //////////////// private Change createRenameChange() { // create a change object for the file that contains the property the // user has selected to rename IFile file = info.getSourceFile(); TextFileChange result = new TextFileChange(file.getName(), file); // a file change contains a tree of edits, first add the root of them MultiTextEdit fileChangeRootEdit = new MultiTextEdit(); result.setEdit(fileChangeRootEdit); // edit object for the text replacement in the file, this is the only child ReplaceEdit edit = new ReplaceEdit(info.getOffset(), info.getOldName().length(), info.getNewName()); fileChangeRootEdit.addChild(edit); return result; } private Change[] createChangesForBundle() { List result = new ArrayList(); Iterator it = phpFiles.keySet().iterator(); while (it.hasNext()) { IFile file = (IFile) it.next(); TextFileChange tfc = new TextFileChange(file.getName(), file); MultiTextEdit fileChangeRootEdit = new MultiTextEdit(); tfc.setEdit(fileChangeRootEdit); // edit object for the text replacement in the file, this is the only // child ReplaceEdit edit = new ReplaceEdit(getKeyOffset(file), info.getOldName().length(), info.getNewName()); fileChangeRootEdit.addChild(edit); result.add(tfc); } return (Change[]) result.toArray(new Change[result.size()]); } private boolean isEmpty(final String candidate) { return candidate == null || candidate.trim().length() == 0; } // private boolean isPropertyKey( final IFile file, final String candidate ) { // boolean result = false; // try { // Properties props = new Properties(); // props.load( file.getContents() ); // result = props.containsKey( candidate ); // } catch( Exception ex ) { // // ignore this, we just assume this is not a favourable situation // ex.printStackTrace(); // } // return result; // } // whether the file is a PHP file with the same base name as the // one we refactor and contains the key that interests us private boolean isToRefactor(final IFile file) { return PHPFileUtil.isPHPFile(file); // && !file.equals( info.getSourceFile() ) // && isPropertyKey( file, info.getOldName() ); } // private String getBundleBaseName() { // String result = info.getSourceFile().getName(); // int underscoreIndex = result.indexOf( '_' ); // if( underscoreIndex != -1 ) { // result = result.substring( 0, underscoreIndex ); // } else { // int index = result.indexOf( EXT_PROPERTIES ) - 1; // result = result.substring( 0, index ); // } // return result; // } private void search(final IContainer rootContainer, final RefactoringStatus status) { try { IResource[] members = rootContainer.members(); for (int i = 0; i < members.length; i++) { if (members[i] instanceof IContainer) { search((IContainer) members[i], status); } else { IFile file = (IFile) members[i]; handleFile(file, status); } } } catch (final CoreException cex) { status.addFatalError(cex.getMessage()); } } private void handleFile(final IFile file, final RefactoringStatus status) { if (isToRefactor(file)) { determineKeyOffsets(file, status); // if (keyOffsets.size() > 0) { // Integer offset = new Integer(keyOffsets); // phpFiles.put(file, offset); // } } } private int getKeyOffset(final IFile file) { return ((Integer) phpFiles.get(file)).intValue(); } // finds the offset of the property key to rename // usually, this would be the job of a proper parser; // using a primitive brute-force approach here private void determineKeyOffsets(final IFile file, final RefactoringStatus status) { String content = readFileContent(file, status); int indx = 0; while ((indx = content.indexOf(info.getOldName(), indx)) >= 0) { phpFiles.put(file, Integer.valueOf(indx++)); } // int result = -1; // int candidateIndex = content.indexOf(info.getOldName()); // result = candidateIndex; // while( result == -1 && candidateIndex != -1 ) { // if( isKeyOccurrence( content, candidateIndex ) ) { // result = candidateIndex; // } // } // if( result == -1 ) { // // still nothing found, we add a warning to the status // // (we have checked the file contains the property, so that we can't // // determine it's offset is probably because of the rough way employed // // here to find it) // String msg = CoreTexts.renamePropertyDelegate_propNotFound // + file.getLocation().toOSString(); // status.addWarning( msg ); // } // return result; } private String readFileContent(final IFile file, final RefactoringStatus refStatus) { String result = null; try { InputStream is = file.getContents(); byte[] buf = new byte[1024]; ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = is.read(buf); while (len > 0) { bos.write(buf, 0, len); len = is.read(buf); } is.close(); result = new String(bos.toByteArray()); } catch (Exception ex) { String msg = ex.toString(); refStatus.addFatalError(msg); String pluginId = PHPeclipsePlugin.getPluginId(); IStatus status = new Status(IStatus.ERROR, pluginId, 0, msg, ex); PHPeclipsePlugin.getDefault().getLog().log(status); } return result; } // we check only that there is a separator before the next line break (this // is not sufficient, the whole thing may be in a comment etc. ...) // private boolean isKeyOccurrence( final String content, // final int candidateIndex ) { // int index = candidateIndex + info.getOldName().length(); // // skip whitespace // while( content.charAt( index ) == ' ' || content.charAt( index ) == '\t' ) // { // index++; // } // return content.charAt( index ) == '=' || content.charAt( index ) == ':'; // } }