001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.corext.refactoring.reorg;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.HashMap;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018:
019: import org.eclipse.text.edits.ReplaceEdit;
020: import org.eclipse.text.edits.TextEdit;
021:
022: import org.eclipse.core.runtime.Assert;
023: import org.eclipse.core.runtime.CoreException;
024: import org.eclipse.core.runtime.IProgressMonitor;
025: import org.eclipse.core.runtime.OperationCanceledException;
026: import org.eclipse.core.runtime.SubProgressMonitor;
027:
028: import org.eclipse.core.resources.IResource;
029:
030: import org.eclipse.ltk.core.refactoring.RefactoringStatus;
031: import org.eclipse.ltk.core.refactoring.TextChange;
032:
033: import org.eclipse.jdt.core.Flags;
034: import org.eclipse.jdt.core.IBuffer;
035: import org.eclipse.jdt.core.ICompilationUnit;
036: import org.eclipse.jdt.core.IImportDeclaration;
037: import org.eclipse.jdt.core.IJavaElement;
038: import org.eclipse.jdt.core.IPackageFragment;
039: import org.eclipse.jdt.core.IType;
040: import org.eclipse.jdt.core.JavaModelException;
041: import org.eclipse.jdt.core.ToolFactory;
042: import org.eclipse.jdt.core.compiler.IScanner;
043: import org.eclipse.jdt.core.compiler.ITerminalSymbols;
044: import org.eclipse.jdt.core.compiler.InvalidInputException;
045: import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
046: import org.eclipse.jdt.core.search.IJavaSearchConstants;
047: import org.eclipse.jdt.core.search.SearchEngine;
048: import org.eclipse.jdt.core.search.SearchMatch;
049: import org.eclipse.jdt.core.search.SearchPattern;
050: import org.eclipse.jdt.core.search.TypeReferenceMatch;
051:
052: import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
053: import org.eclipse.jdt.internal.corext.refactoring.CollectingSearchRequestor;
054: import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
055: import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory;
056: import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine;
057: import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
058: import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility;
059: import org.eclipse.jdt.internal.corext.refactoring.structure.ReferenceFinderUtil;
060: import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
061: import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
062: import org.eclipse.jdt.internal.corext.util.Messages;
063: import org.eclipse.jdt.internal.corext.util.SearchUtils;
064:
065: import org.eclipse.jdt.internal.ui.JavaPlugin;
066:
067: public class MoveCuUpdateCreator {
068:
069: private final String fNewPackage;
070: private ICompilationUnit[] fCus;
071: private IPackageFragment fDestination;
072:
073: private Map fImportRewrites; //ICompilationUnit -> ImportEdit
074:
075: public MoveCuUpdateCreator(ICompilationUnit cu,
076: IPackageFragment pack) {
077: this (new ICompilationUnit[] { cu }, pack);
078: }
079:
080: public MoveCuUpdateCreator(ICompilationUnit[] cus,
081: IPackageFragment pack) {
082: Assert.isNotNull(cus);
083: Assert.isNotNull(pack);
084: fCus = cus;
085: fDestination = pack;
086: fImportRewrites = new HashMap();
087: fNewPackage = fDestination.isDefaultPackage() ? "" : fDestination.getElementName() + '.'; //$NON-NLS-1$
088: }
089:
090: public TextChangeManager createChangeManager(IProgressMonitor pm,
091: RefactoringStatus status) throws JavaModelException {
092: pm.beginTask("", 5); //$NON-NLS-1$
093: try {
094: TextChangeManager changeManager = new TextChangeManager();
095: addUpdates(changeManager, new SubProgressMonitor(pm, 4),
096: status);
097: addImportRewriteUpdates(changeManager);
098: return changeManager;
099: } catch (JavaModelException e) {
100: throw e;
101: } catch (CoreException e) {
102: throw new JavaModelException(e);
103: } finally {
104: pm.done();
105: }
106:
107: }
108:
109: private void addImportRewriteUpdates(TextChangeManager changeManager)
110: throws CoreException {
111: for (Iterator iter = fImportRewrites.keySet().iterator(); iter
112: .hasNext();) {
113: ICompilationUnit cu = (ICompilationUnit) iter.next();
114: ImportRewrite importRewrite = (ImportRewrite) fImportRewrites
115: .get(cu);
116: if (importRewrite != null
117: && importRewrite.hasRecordedChanges()) {
118: TextChangeCompatibility
119: .addTextEdit(
120: changeManager.get(cu),
121: RefactoringCoreMessages.MoveCuUpdateCreator_update_imports,
122: importRewrite.rewriteImports(null));
123: }
124: }
125: }
126:
127: private void addUpdates(TextChangeManager changeManager,
128: IProgressMonitor pm, RefactoringStatus status)
129: throws CoreException {
130: pm.beginTask("", fCus.length); //$NON-NLS-1$
131: for (int i = 0; i < fCus.length; i++) {
132: if (pm.isCanceled())
133: throw new OperationCanceledException();
134:
135: addUpdates(changeManager, fCus[i], new SubProgressMonitor(
136: pm, 1), status);
137: }
138: }
139:
140: private void addUpdates(TextChangeManager changeManager,
141: ICompilationUnit movedUnit, IProgressMonitor pm,
142: RefactoringStatus status) throws CoreException {
143: try {
144: pm.beginTask("", 3); //$NON-NLS-1$
145: pm
146: .subTask(Messages
147: .format(
148: RefactoringCoreMessages.MoveCuUpdateCreator_searching,
149: movedUnit.getElementName()));
150:
151: if (isInAnotherFragmentOfSamePackage(movedUnit,
152: fDestination)) {
153: pm.worked(3);
154: return;
155: }
156:
157: addImportToSourcePackageTypes(movedUnit,
158: new SubProgressMonitor(pm, 1));
159: removeImportsToDestinationPackageTypes(movedUnit);
160: addReferenceUpdates(changeManager, movedUnit,
161: new SubProgressMonitor(pm, 2), status);
162: } finally {
163: pm.done();
164: }
165: }
166:
167: private void addReferenceUpdates(TextChangeManager changeManager,
168: ICompilationUnit movedUnit, IProgressMonitor pm,
169: RefactoringStatus status) throws JavaModelException,
170: CoreException {
171: List cuList = Arrays.asList(fCus);
172: SearchResultGroup[] references = getReferences(movedUnit, pm,
173: status);
174: for (int i = 0; i < references.length; i++) {
175: SearchResultGroup searchResultGroup = references[i];
176: ICompilationUnit referencingCu = searchResultGroup
177: .getCompilationUnit();
178: if (referencingCu == null)
179: continue;
180:
181: boolean simpleReferencesNeedNewImport = simpleReferencesNeedNewImport(
182: movedUnit, referencingCu, cuList);
183: SearchMatch[] results = searchResultGroup
184: .getSearchResults();
185: for (int j = 0; j < results.length; j++) {
186: // TODO: should update type references with results from addImport
187: TypeReference reference = (TypeReference) results[j];
188: if (reference.isImportDeclaration()) {
189: ImportRewrite rewrite = getImportRewrite(referencingCu);
190: IImportDeclaration importDecl = (IImportDeclaration) SearchUtils
191: .getEnclosingJavaElement(results[j]);
192: if (Flags.isStatic(importDecl.getFlags())) {
193: rewrite.removeStaticImport(importDecl
194: .getElementName());
195: addStaticImport(movedUnit, importDecl, rewrite);
196: } else {
197: rewrite.removeImport(importDecl
198: .getElementName());
199: rewrite.addImport(createStringForNewImport(
200: movedUnit, importDecl));
201: }
202: } else if (reference.isQualified()) {
203: TextChange textChange = changeManager
204: .get(referencingCu);
205: String changeName = RefactoringCoreMessages.MoveCuUpdateCreator_update_references;
206: TextEdit replaceEdit = new ReplaceEdit(reference
207: .getOffset(), reference
208: .getSimpleNameStart()
209: - reference.getOffset(), fNewPackage);
210: TextChangeCompatibility.addTextEdit(textChange,
211: changeName, replaceEdit);
212: } else if (simpleReferencesNeedNewImport) {
213: ImportRewrite importEdit = getImportRewrite(referencingCu);
214: String typeName = reference.getSimpleName();
215: importEdit.addImport(getQualifiedType(fDestination
216: .getElementName(), typeName));
217: }
218: }
219: }
220: }
221:
222: private void addStaticImport(ICompilationUnit movedUnit,
223: IImportDeclaration importDecl, ImportRewrite rewrite) {
224: String old = importDecl.getElementName();
225: int oldPackLength = movedUnit.getParent().getElementName()
226: .length();
227:
228: StringBuffer result = new StringBuffer(fDestination
229: .getElementName());
230: if (oldPackLength == 0) // move FROM default package
231: result.append('.').append(old);
232: else if (result.length() == 0) // move TO default package
233: result.append(old.substring(oldPackLength + 1)); // cut "."
234: else
235: result.append(old.substring(oldPackLength));
236: int index = result.lastIndexOf("."); //$NON-NLS-1$
237: if (index > 0 && index < result.length() - 1)
238: rewrite.addStaticImport(result.substring(0, index), result
239: .substring(index + 1, result.length()), true);
240: }
241:
242: private String getQualifiedType(String packageName, String typeName) {
243: if (packageName.length() == 0)
244: return typeName;
245: else
246: return packageName + '.' + typeName;
247: }
248:
249: private String createStringForNewImport(ICompilationUnit movedUnit,
250: IImportDeclaration importDecl) {
251: String old = importDecl.getElementName();
252: int oldPackLength = movedUnit.getParent().getElementName()
253: .length();
254:
255: StringBuffer result = new StringBuffer(fDestination
256: .getElementName());
257: if (oldPackLength == 0) // move FROM default package
258: result.append('.').append(old);
259: else if (result.length() == 0) // move TO default package
260: result.append(old.substring(oldPackLength + 1)); // cut "."
261: else
262: result.append(old.substring(oldPackLength));
263: return result.toString();
264: }
265:
266: private void removeImportsToDestinationPackageTypes(
267: ICompilationUnit movedUnit) throws CoreException {
268: ImportRewrite importEdit = getImportRewrite(movedUnit);
269: IType[] destinationTypes = getDestinationPackageTypes();
270: for (int i = 0; i < destinationTypes.length; i++) {
271: importEdit.removeImport(JavaModelUtil
272: .getFullyQualifiedName(destinationTypes[i]));
273: }
274: }
275:
276: private IType[] getDestinationPackageTypes()
277: throws JavaModelException {
278: List types = new ArrayList();
279: if (fDestination.exists()) {
280: ICompilationUnit[] cus = fDestination.getCompilationUnits();
281: for (int i = 0; i < cus.length; i++) {
282: types.addAll(Arrays.asList(cus[i].getAllTypes()));
283: }
284: }
285: return (IType[]) types.toArray(new IType[types.size()]);
286: }
287:
288: private void addImportToSourcePackageTypes(
289: ICompilationUnit movedUnit, IProgressMonitor pm)
290: throws CoreException {
291: List cuList = Arrays.asList(fCus);
292: IType[] allCuTypes = movedUnit.getAllTypes();
293: IType[] referencedTypes = ReferenceFinderUtil
294: .getTypesReferencedIn(allCuTypes, pm);
295: ImportRewrite importEdit = getImportRewrite(movedUnit);
296: importEdit.setFilterImplicitImports(false);
297: IPackageFragment srcPack = (IPackageFragment) movedUnit
298: .getParent();
299: for (int i = 0; i < referencedTypes.length; i++) {
300: IType iType = referencedTypes[i];
301: if (!iType.exists())
302: continue;
303: if (!iType.getPackageFragment().equals(srcPack))
304: continue;
305: if (cuList.contains(iType.getCompilationUnit()))
306: continue;
307: importEdit.addImport(JavaModelUtil
308: .getFullyQualifiedName(iType));
309: }
310: }
311:
312: private ImportRewrite getImportRewrite(ICompilationUnit cu)
313: throws CoreException {
314: if (fImportRewrites.containsKey(cu))
315: return (ImportRewrite) fImportRewrites.get(cu);
316: ImportRewrite importEdit = StubUtility.createImportRewrite(cu,
317: true);
318: fImportRewrites.put(cu, importEdit);
319: return importEdit;
320: }
321:
322: private boolean simpleReferencesNeedNewImport(
323: ICompilationUnit movedUnit, ICompilationUnit referencingCu,
324: List cuList) {
325: if (referencingCu.equals(movedUnit))
326: return false;
327: if (cuList.contains(referencingCu))
328: return false;
329: if (isReferenceInAnotherFragmentOfSamePackage(referencingCu,
330: movedUnit)) {
331: /* Destination package is different from source, since
332: * isDestinationAnotherFragmentOfSamePackage(movedUnit) was false in addUpdates(.) */
333: return true;
334: }
335:
336: //heuristic
337: if (referencingCu.getImport(
338: movedUnit.getParent().getElementName() + ".*").exists()) //$NON-NLS-1$
339: return true; // has old star import
340: if (referencingCu.getParent().equals(movedUnit.getParent()))
341: return true; //is moved away from same package
342: return false;
343: }
344:
345: private boolean isReferenceInAnotherFragmentOfSamePackage(
346: ICompilationUnit referencingCu, ICompilationUnit movedUnit) {
347: if (referencingCu == null)
348: return false;
349: if (!(referencingCu.getParent() instanceof IPackageFragment))
350: return false;
351: IPackageFragment pack = (IPackageFragment) referencingCu
352: .getParent();
353: return isInAnotherFragmentOfSamePackage(movedUnit, pack);
354: }
355:
356: private static boolean isInAnotherFragmentOfSamePackage(
357: ICompilationUnit cu, IPackageFragment pack) {
358: if (!(cu.getParent() instanceof IPackageFragment))
359: return false;
360: IPackageFragment cuPack = (IPackageFragment) cu.getParent();
361: return !cuPack.equals(pack)
362: && JavaModelUtil.isSamePackage(cuPack, pack);
363: }
364:
365: private static SearchResultGroup[] getReferences(
366: ICompilationUnit unit, IProgressMonitor pm,
367: RefactoringStatus status) throws CoreException {
368: final SearchPattern pattern = RefactoringSearchEngine
369: .createOrPattern(unit.getTypes(),
370: IJavaSearchConstants.REFERENCES);
371: if (pattern != null)
372: return RefactoringSearchEngine
373: .search(pattern, RefactoringScopeFactory
374: .create(unit), new Collector(
375: ((IPackageFragment) unit.getParent())),
376: new SubProgressMonitor(pm, 1), status);
377: return new SearchResultGroup[] {};
378: }
379:
380: private final static class Collector extends
381: CollectingSearchRequestor {
382: private IPackageFragment fSource;
383: private IScanner fScanner;
384:
385: public Collector(IPackageFragment source) {
386: fSource = source;
387: fScanner = ToolFactory.createScanner(false, false, false,
388: false);
389: }
390:
391: /* (non-Javadoc)
392: * @see org.eclipse.jdt.internal.corext.refactoring.CollectingSearchRequestor#acceptSearchMatch(SearchMatch)
393: */
394: public void acceptSearchMatch(SearchMatch match)
395: throws CoreException {
396: /*
397: * Processing is done in collector to reuse the buffer which was
398: * already required by the search engine to locate the matches.
399: */
400: // [start, end[ include qualification.
401: IJavaElement element = SearchUtils
402: .getEnclosingJavaElement(match);
403: int accuracy = match.getAccuracy();
404: int start = match.getOffset();
405: int length = match.getLength();
406: boolean insideDocComment = match.isInsideDocComment();
407: IResource res = match.getResource();
408: if (element.getAncestor(IJavaElement.IMPORT_DECLARATION) != null) {
409: super .acceptSearchMatch(TypeReference
410: .createImportReference(element, accuracy,
411: start, length, insideDocComment, res));
412: } else {
413: ICompilationUnit unit = (ICompilationUnit) element
414: .getAncestor(IJavaElement.COMPILATION_UNIT);
415: if (unit != null) {
416: IBuffer buffer = unit.getBuffer();
417: String matchText = buffer.getText(start, length);
418: if (fSource.isDefaultPackage()) {
419: super .acceptSearchMatch(TypeReference
420: .createSimpleReference(element,
421: accuracy, start, length,
422: insideDocComment, res,
423: matchText));
424: } else {
425: // assert: matchText doesn't start nor end with comment
426: int simpleNameStart = getLastSimpleNameStart(matchText);
427: if (simpleNameStart != 0) {
428: super .acceptSearchMatch(TypeReference
429: .createQualifiedReference(element,
430: accuracy, start, length,
431: insideDocComment, res,
432: start + simpleNameStart));
433: } else {
434: super .acceptSearchMatch(TypeReference
435: .createSimpleReference(element,
436: accuracy, start, length,
437: insideDocComment, res,
438: matchText));
439: }
440: }
441: }
442: }
443: }
444:
445: private int getLastSimpleNameStart(String reference) {
446: fScanner.setSource(reference.toCharArray());
447: int lastIdentifierStart = -1;
448: try {
449: int tokenType = fScanner.getNextToken();
450: while (tokenType != ITerminalSymbols.TokenNameEOF) {
451: if (tokenType == ITerminalSymbols.TokenNameIdentifier)
452: lastIdentifierStart = fScanner
453: .getCurrentTokenStartPosition();
454: tokenType = fScanner.getNextToken();
455: }
456: } catch (InvalidInputException e) {
457: JavaPlugin.log(e);
458: }
459: return lastIdentifierStart;
460: }
461: }
462:
463: private final static class TypeReference extends TypeReferenceMatch {
464: private String fSimpleTypeName;
465: private int fSimpleNameStart;
466:
467: private TypeReference(IJavaElement enclosingElement,
468: int accuracy, int start, int length,
469: boolean insideDocComment, IResource resource,
470: int simpleNameStart, String simpleName) {
471: super (enclosingElement, accuracy, start, length,
472: insideDocComment, SearchEngine
473: .getDefaultSearchParticipant(), resource);
474: fSimpleNameStart = simpleNameStart;
475: fSimpleTypeName = simpleName;
476: }
477:
478: public static TypeReference createQualifiedReference(
479: IJavaElement enclosingElement, int accuracy, int start,
480: int length, boolean insideDocComment,
481: IResource resource, int simpleNameStart) {
482: Assert.isTrue(start < simpleNameStart
483: && simpleNameStart < start + length);
484: return new TypeReference(enclosingElement, accuracy, start,
485: length, insideDocComment, resource,
486: simpleNameStart, null);
487: }
488:
489: public static TypeReference createImportReference(
490: IJavaElement enclosingElement, int accuracy, int start,
491: int length, boolean insideDocComment, IResource resource) {
492: return new TypeReference(enclosingElement, accuracy, start,
493: length, insideDocComment, resource, -1, null);
494: }
495:
496: public static TypeReference createSimpleReference(
497: IJavaElement enclosingElement, int accuracy, int start,
498: int length, boolean insideDocComment,
499: IResource resource, String simpleName) {
500: return new TypeReference(enclosingElement, accuracy, start,
501: length, insideDocComment, resource, -1, simpleName);
502: }
503:
504: public boolean isImportDeclaration() {
505: return SearchUtils.getEnclosingJavaElement(this )
506: .getAncestor(IJavaElement.IMPORT_DECLARATION) != null;
507: }
508:
509: public boolean isQualified() {
510: return fSimpleNameStart != -1;
511: }
512:
513: public boolean isSimpleReference() {
514: return fSimpleTypeName != null;
515: }
516:
517: /**
518: * @return start offset of simple type name, or -1 iff ! isQualified()
519: */
520: public int getSimpleNameStart() {
521: return fSimpleNameStart;
522: }
523:
524: /**
525: * @return simple type name, or null iff ! isSimpleName()
526: */
527: public String getSimpleName() {
528: return fSimpleTypeName;
529: }
530: }
531:
532: }
|