/******************************************************************************* * Copyright (c) 2009 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.seam.internal.core.refactoring; import java.util.ArrayList; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; 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.RefactoringParticipant; import org.eclipse.ltk.core.refactoring.participants.RenameProcessor; import org.eclipse.osgi.util.NLS; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.jboss.tools.common.el.core.model.ELInvocationExpression; import org.jboss.tools.common.el.core.model.ELPropertyInvocation; import org.jboss.tools.common.el.core.resolver.ELResolver; import org.jboss.tools.common.el.core.resolver.IRelevanceCheck; import org.jboss.tools.common.text.ITextSourceReference; import org.jboss.tools.common.util.FileUtil; import org.jboss.tools.seam.core.BijectedAttributeType; import org.jboss.tools.seam.core.IBijectedAttribute; import org.jboss.tools.seam.core.ISeamComponent; import org.jboss.tools.seam.core.ISeamFactory; import org.jboss.tools.seam.core.ISeamJavaComponentDeclaration; import org.jboss.tools.seam.core.ISeamProject; import org.jboss.tools.seam.core.ISeamXmlComponentDeclaration; import org.jboss.tools.seam.core.SeamCoreMessages; import org.jboss.tools.seam.core.SeamCorePlugin; import org.jboss.tools.seam.core.SeamProjectsSet; import org.jboss.tools.seam.core.SeamUtil; import org.jboss.tools.seam.internal.core.SeamComponentDeclaration; import org.jboss.tools.seam.internal.core.scanner.java.SeamAnnotations; /** * @author Daniel Azarov */ public abstract class SeamRenameProcessor extends RenameProcessor { protected static final String JAVA_EXT = "java"; //$NON-NLS-1$ protected static final String XML_EXT = "xml"; //$NON-NLS-1$ protected static final String XHTML_EXT = "xhtml"; //$NON-NLS-1$ protected static final String JSP_EXT = "jsp"; //$NON-NLS-1$ protected static final String PROPERTIES_EXT = "properties"; //$NON-NLS-1$ protected static final RefactoringParticipant[] EMPTY_REF_PARTICIPANT = new RefactoringParticipant[0]; protected static final String SEAM_PROPERTIES_FILE = "seam.properties"; //$NON-NLS-1$ protected RefactoringStatus status; protected CompositeChange rootChange; protected TextFileChange lastChange; protected IFile declarationFile=null; protected SeamProjectsSet projectsSet; private String newName; private String oldName; private SeamSearcher searcher = null; protected ISeamComponent component; protected SeamSearcher getSearcher(){ if(searcher == null){ searcher = new SeamSearcher(declarationFile, getOldName()); } return searcher; } public void setNewName(String newName){ this.newName = newName; } protected String getNewName(){ return newName; } protected void setOldName(String oldName){ this.oldName = oldName; } public String getOldName(){ return oldName; } // lets collect all changes for the same files in one MultiTextEdit protected TextFileChange getChange(IFile file){ if(lastChange != null && lastChange.getFile().equals(file)) return lastChange; for(int i=0; i < rootChange.getChildren().length; i++){ TextFileChange change = (TextFileChange)rootChange.getChildren()[i]; if(change.getFile().equals(file)){ lastChange = change; return lastChange; } } lastChange = new TextFileChange(file.getName(), file); MultiTextEdit root = new MultiTextEdit(); lastChange.setEdit(root); rootChange.add(lastChange); return lastChange; } protected void findDeclarations(ISeamComponent component) throws CoreException{ changeDeclarations(component); if(declarationFile == null) return; projectsSet = new SeamProjectsSet(declarationFile.getProject()); IProject[] projects = projectsSet.getAllProjects(); for (IProject project : projects) { ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true); if(seamProject != null){ ISeamComponent comp = seamProject.getComponent(getOldName()); if(comp != null) changeDeclarations(comp); } } } protected void findAnnotations(){ if(declarationFile == null){ status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_DECLARATION_NOT_FOUND, getOldName())); return; } ISeamProject seamProject = SeamCorePlugin.getSeamProject(declarationFile.getProject(), true); if(seamProject == null) return; findInFactoryAnnotations(seamProject); IProject[] projects = projectsSet.getAllProjects(); for (IProject project : projects) { ISeamProject sProject = SeamCorePlugin.getSeamProject(project, true); if(sProject != null){ findInFactoryAnnotations(sProject); } } } private void findInFactoryAnnotations(ISeamProject seamProject){ // find @In annotations findAnnotations(seamProject, BijectedAttributeType.IN, SeamAnnotations.IN_ANNOTATION_TYPE); findFactories(seamProject); } private void findFactories(ISeamProject seamProject){ // find @Factory annotations Set<ISeamFactory> factorySet = seamProject.getFactoriesByName(getOldName()); for(ISeamFactory factory : factorySet){ changeFactory(factory); } } private void findAnnotations(ISeamProject seamProject, BijectedAttributeType type, String locationPath){ Set<IBijectedAttribute> attributes = seamProject.getBijectedAttributesByName(getOldName(), type); for(IBijectedAttribute attribute : attributes){ ITextSourceReference location = attribute.getLocationFor(locationPath); if(!changeAnnotation(location, (IFile)attribute.getResource())){ status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_OUT_OF_SYNC_PROJECT, seamProject.getProject().getFullPath().toString())); return; } } } private void changeFactory(ISeamFactory factory){ IFile file = (IFile)factory.getResource(); if(file.getFileExtension().equalsIgnoreCase(JAVA_EXT)){ ITextSourceReference location = factory.getLocationFor(SeamAnnotations.FACTORY_ANNOTATION_TYPE); if(!changeAnnotation(location, file)){ status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_OUT_OF_SYNC_PROJECT, file.getProject().getFullPath().toString())); return; } }else{ ITextSourceReference location = factory.getLocationFor(ISeamXmlComponentDeclaration.NAME); if(!changeXMLNode(location, file)){ status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_OUT_OF_SYNC_PROJECT, file.getProject().getFullPath().toString())); return; } } } private boolean isBadLocation(ITextSourceReference location, IFile file){ boolean flag; if(location == null) flag = true; else flag = location.getStartPosition() == 0 && location.getLength() == 0; if(flag) status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_LOCATION_NOT_FOUND, file.getFullPath().toString())); return flag; } private boolean changeXMLNode(ITextSourceReference location, IFile file){ if(isBadLocation(location, file)) return true; if(file.isPhantom()) return true; if(!file.isSynchronized(IResource.DEPTH_ZERO)) return false; String content = null; try { content = FileUtil.readStream(file); } catch (CoreException e) { SeamCorePlugin.getDefault().logError(e); } String text = content.substring(location.getStartPosition(), location.getStartPosition()+location.getLength()); if(text.startsWith("<")){ //$NON-NLS-1$ int position = text.lastIndexOf("/>"); //$NON-NLS-1$ if(position < 0){ position = text.lastIndexOf(">"); //$NON-NLS-1$ } change(file, location.getStartPosition()+position, 0, " name=\""+getNewName()+"\""); //$NON-NLS-1$ //$NON-NLS-2$ }else{ change(file, location.getStartPosition(), location.getLength(), getNewName()); } return true; } private boolean changeAnnotation(ITextSourceReference location, IFile file){ if(isBadLocation(location, file)) return true; if(file.isPhantom()) return true; if(!file.isSynchronized(IResource.DEPTH_ZERO)) return false; String content = null; try { content = FileUtil.readStream(file); } catch (CoreException e) { SeamCorePlugin.getDefault().logError(e); } String text = content.substring(location.getStartPosition(), location.getStartPosition()+location.getLength()); int openBracket = text.indexOf("("); //$NON-NLS-1$ int openQuote = text.indexOf("\""); //$NON-NLS-1$ if(openBracket >= 0){ int closeBracket = text.indexOf(")", openBracket); //$NON-NLS-1$ int equals = text.indexOf("=", openBracket); //$NON-NLS-1$ int value = text.indexOf("value", openBracket); //$NON-NLS-1$ if(closeBracket == openBracket+1){ // empty brackets String newText = "\""+getNewName()+"\""; //$NON-NLS-1$ //$NON-NLS-2$ change(file, location.getStartPosition()+openBracket+1, 0, newText); }else if(value > 0){ // construction value="name" found so change name String newText = text.replace(getOldName(), getNewName()); change(file, location.getStartPosition(), location.getLength(), newText); }else if(equals > 0){ // other parameters are found String newText = "value=\""+getNewName()+"\","; //$NON-NLS-1$ //$NON-NLS-2$ change(file, location.getStartPosition()+openBracket+1, 0, newText); }else{ // other cases String newText = text.replace(getOldName(), getNewName()); change(file, location.getStartPosition(), location.getLength(), newText); } }else if(openQuote >= 0){ int closeQuota = text.indexOf("\"", openQuote); //$NON-NLS-1$ if(closeQuota == openQuote+1){ // empty quotas String newText = "\""+getNewName()+"\""; //$NON-NLS-1$ //$NON-NLS-2$ change(file, location.getStartPosition()+openQuote+1, 0, newText); }else{ // the other cases String newText = text.replace(getOldName(), getNewName()); change(file, location.getStartPosition(), location.getLength(), newText); } }else{ String newText = "(\""+getNewName()+"\")"; //$NON-NLS-1$ //$NON-NLS-2$ change(file, location.getStartPosition()+location.getLength(), 0, newText); } return true; } private void changeDeclarations(ISeamComponent component) throws CoreException{ if(component.getJavaDeclaration() != null) renameJavaDeclaration(component.getJavaDeclaration()); Set<ISeamXmlComponentDeclaration> xmlDecls = component.getXmlDeclarations(); for(ISeamXmlComponentDeclaration xmlDecl : xmlDecls){ renameXMLDeclaration(xmlDecl); } } protected void checkDeclarations(ISeamComponent component) throws CoreException{ if(component.getJavaDeclaration() != null){ if(component.getJavaDeclaration().getResource() == null) status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_COMPONENT_HAS_BROKEN_DECLARATION, new String[]{component.getName()})); else if(SeamUtil.isJar(component.getJavaDeclaration()) && component.getJavaDeclaration().getName() != null) status.addInfo(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_COMPONENT_HAS_DECLARATION_FROM_JAR, new String[]{component.getName(), component.getJavaDeclaration().getResource().getFullPath().toString()})); } Set<ISeamXmlComponentDeclaration> xmlDecls = component.getXmlDeclarations(); for(ISeamXmlComponentDeclaration xmlDecl : xmlDecls){ if(xmlDecl.getResource() == null) status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_COMPONENT_HAS_BROKEN_DECLARATION, new String[]{component.getName()})); else if(SeamUtil.isJar(xmlDecl) && xmlDecl.getName() != null) status.addInfo(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_COMPONENT_HAS_DECLARATION_FROM_JAR, new String[]{component.getName(), xmlDecl.getResource().getFullPath().toString()})); } } // protected boolean isFileCorrect(IFile file){ // if(!file.isSynchronized(IResource.DEPTH_ZERO)){ // status.addFatalError(Messages.format(SeamCoreMessages.SEAM_RENAME_PROCESSOR_OUT_OF_SYNC_PROJECT, file.getProject().getFullPath().toString())); // return false; // }else if(file.isPhantom()){ // return false; // }else if(file.isReadOnly()){ // status.addFatalError(Messages.format(SeamCoreMessages.SEAM_RENAME_PROCESSOR_ERROR_READ_ONLY_FILE, file.getFullPath().toString())); // return false; // } // return true; // } private void renameJavaDeclaration(ISeamJavaComponentDeclaration javaDecl) throws CoreException{ IFile file = (IFile)javaDecl.getResource(); if(file != null && !SeamUtil.isJar(javaDecl)){ ITextSourceReference location = ((SeamComponentDeclaration)javaDecl).getLocationFor(ISeamXmlComponentDeclaration.NAME); if(location != null && !isBadLocation(location, file)) change(file, location.getStartPosition(), location.getLength(), "\""+getNewName()+"\""); //$NON-NLS-1$ //$NON-NLS-2$ } declarationFile = file; } private void renameXMLDeclaration(ISeamXmlComponentDeclaration xmlDecl) throws CoreException{ IFile file = (IFile)xmlDecl.getResource(); if(file != null && !SeamUtil.isJar(xmlDecl)){ ITextSourceReference location = ((SeamComponentDeclaration)xmlDecl).getLocationFor(ISeamXmlComponentDeclaration.NAME); if(location != null && !isBadLocation(location, file)) changeXMLNode(location, file); } if(declarationFile == null) declarationFile = file; } protected void renameComponent(IProgressMonitor pm, ISeamComponent component)throws CoreException{ pm.beginTask("", 3); clearChanges(); findDeclarations(component); if(status.hasFatalError()) return; pm.worked(1); findAnnotations(); if(status.hasFatalError()) return; pm.worked(1); getSearcher().findELReferences(pm); pm.done(); } protected void renameSeamContextVariable(IProgressMonitor pm, IFile sourceFile)throws CoreException{ pm.beginTask("", 2); clearChanges(); declarationFile = sourceFile; findOutDataModelFactory(); if(status.hasFatalError()) return; pm.worked(1); getSearcher().findELReferences(pm); pm.done(); } protected void findOutDataModelFactory(){ if(declarationFile == null) return; ISeamProject seamProject = SeamCorePlugin.getSeamProject(declarationFile.getProject(), true); if(seamProject == null) return; IProject[] projects = projectsSet.getAllProjects(); for (IProject project : projects) { ISeamProject sProject = SeamCorePlugin.getSeamProject(project, true); if(sProject != null){ findAnnotations(sProject, BijectedAttributeType.OUT, SeamAnnotations.OUT_ANNOTATION_TYPE); findAnnotations(sProject, BijectedAttributeType.DATA_BINDER, SeamAnnotations.DATA_MODEL_ANNOTATION_TYPE); findFactories(sProject); } } } ArrayList<String> keys = new ArrayList<String>(); private void clearChanges(){ keys.clear(); } private void change(IFile file, int offset, int length, String text){ if(file.isReadOnly()){ status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_ERROR_READ_ONLY_FILE, file.getFullPath().toString())); return; } String key = file.getFullPath().toString()+" "+offset; if(!keys.contains(key)){ TextFileChange change = getChange(file); TextEdit edit = new ReplaceEdit(offset, length, text); change.addEdit(edit); keys.add(key); } } class SeamSearcher extends SeamRefactorSearcher{ public SeamSearcher(IFile declarationFile, String oldName){ super(declarationFile, oldName/*, component*/); } protected IRelevanceCheck[] getRelevanceChecks(ELResolver[] resolvers) { if(resolvers == null) return new IRelevanceCheck[0]; IRelevanceCheck[] result = new IRelevanceCheck[resolvers.length]; IRelevanceCheck check = new IRelevanceCheck() { public boolean isRelevant(String content) { if(content == null) return true; return content.indexOf(oldName) >= 0; } }; for (int i = 0; i < result.length; i++) result[i] = check; return result; } @Override protected void outOfSynch(IResource resource) { status.addWarning(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_OUT_OF_SYNC_PROJECT, resource.getFullPath().toString())); } @Override protected void match(IFile file, int offset, int length) { if(isFileReadOnly(file)){ status.addFatalError(NLS.bind(SeamCoreMessages.SEAM_RENAME_PROCESSOR_ERROR_READ_ONLY_FILE, file.getFullPath().toString())); }else change(file, offset, length, newName); } protected ELInvocationExpression findComponentReference(ELInvocationExpression invocationExpression){ if(seamComponent != null) return invocationExpression; ELInvocationExpression invExp = invocationExpression; while(invExp != null){ if(invExp instanceof ELPropertyInvocation){ if(((ELPropertyInvocation)invExp).getQualifiedName() != null && ((ELPropertyInvocation)invExp).getQualifiedName().equals(propertyName)) return invExp; else invExp = invExp.getLeft(); }else{ invExp = invExp.getLeft(); } } return null; } } }