/*******************************************************************************
* Copyright (c) 2005, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ltk.core.refactoring;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ltk.core.refactoring.history.IRefactoringExecutionListener;
import org.eclipse.ltk.core.refactoring.history.IRefactoringHistoryListener;
import org.eclipse.ltk.core.refactoring.history.IRefactoringHistoryService;
/**
* Descriptor object of a refactoring.
* <p>
* A refactoring descriptor contains refactoring-specific data which allows the framework to
* completely reconstruct a particular refactoring instance and execute it on an arbitrary
* workspace.
* </p>
* <p>
* Refactoring descriptors contain the following information:
* <ul>
* <li>a short description string, which provides a human-readable text designed to be displayed in
* the user interface to represent the refactoring in trees and lists. Descriptions are
* automatically generated by refactorings.</li>
* <li>an optional comment string, which provides a full human-readable description of the
* refactoring. Comments are automatically generated by refactorings and provide more
* refactoring-specific information, such as which elements have participated in the refactoring.</li>
* <li>refactoring descriptor flags, which tell the framework what capabilities or properties a
* certain refactorings has when executed in a remote context.</li>
* <li>a timestamp, measured as the milliseconds since January 1, 1970, 00:00:00 GMT, which denotes
* the original execution time of the refactoring.</li>
* <li>a unique ID, which denotes a certain kind of refactoring (e.g. Rename File). This ID is
* usually composed of the plugin identifier of the contributing plugin and a plugin-wide unique
* identifier (e.g. <code>org.eclipse.ltk.core.refactoring.renameFile</code>).</li>
* <li>the optional name of the project this refactoring is associated with. Note that the project
* name is not available if the refactoring cannot be associated with a single project, or the
* refactoring descriptor has been read from a file which cannot be associated with a project.</li>
* </ul>
* </p>
* <p>
* Refactoring descriptors are identified by their refactoring id {@link #getID()} and their time
* stamps {@link #getTimeStamp()} and are potentially heavy weight objects which should not be held
* on to. Use refactoring descriptor proxies {@link RefactoringDescriptorProxy} to present
* refactoring descriptors in the user interface or otherwise manipulate refactoring histories.
* </p>
* <p>
* Clients which create specific refactoring descriptors during change generation should choose a
* short, informative and human-readable description of the particular refactoring instance and pass
* appropriate descriptor flags to the constructor. More details about a particular refactoring can
* be revealed in the comment, which contains more text with refactoring-specific information.
* </p>
* <p>
* Refactoring descriptors do not provide version information. It is the responsibility of the
* client to enhance subclasses with refactoring version information in order to provide a means of
* schema evolution.
* </p>
* <p>
* All time stamps are measured as the milliseconds since January 1, 1970, 00:00:00 GMT.
* </p>
* <p>
* Note: this class is indented to be subclassed by clients to provide specialized refactoring
* descriptors for particular refactorings.
* </p>
*
* @see RefactoringDescriptorProxy
* @see IRefactoringHistoryService
*
* @since 3.2
*
* @author Mohsen Vakilian, nchen - Added method stub for subclasses to add more information by
* cloning an existing RefactoringDescriptor
*/
public abstract class RefactoringDescriptor implements Comparable {
/**
* Constant describing the API change flag (value: <code>1</code>).
* <p>
* Clients should set this flag to indicate that the represented refactoring may cause breaking
* API changes. If clients set the {@link #BREAKING_CHANGE} flag, they should set
* {@link #STRUCTURAL_CHANGE} as well. Typically, refactorings which change elements that are
* marked as API according to the semantics of the associated programming language should set
* this flag. This flag is used by the refactoring framework to determine whether a refactoring
* may break existing API when replayed by clients.
* </p>
*/
public static final int BREAKING_CHANGE= 1 << 0;
/**
* The unknown refactoring id (value: <code>org.eclipse.ltk.core.refactoring.unknown</code>).
* <p>
* This id is reserved by the refactoring framework to signal that a refactoring has been
* performed which did not deliver a refactoring descriptor via its
* {@link Change#getDescriptor()} method. The refactoring history service never returns unknown
* refactorings. For consistency reasons, they are reported for
* {@link IRefactoringExecutionListener} or {@link IRefactoringHistoryListener} in order to keep
* clients of these listeners synchronized with the workbench's operation history.
* </p>
*/
public static final String ID_UNKNOWN= "org.eclipse.ltk.core.refactoring.unknown"; //$NON-NLS-1$
/**
* Constant describing the multi change flag (value: <code>4</code>).
* <p>
* Clients should set this flag to indicate that the change created by the represented
* refactoring might causes changes in other files than the files of the input elements
* according to the semantics of the associated programming language. Typically, refactorings
* which update references to the refactored element should set this flag. This flag is used
* during team synchronize operations to optimize the processing of refactorings.
* </p>
*/
public static final int MULTI_CHANGE= 1 << 2;
/** Constant describing the absence of any flags (value: <code>0</code>). */
public static final int NONE= 0;
/**
* Constant describing the structural change flag (value: <code>2</code>).
* <p>
* Clients should set this flag to indicate that the change created by the represented
* refactoring might be a structural change according to the semantics of the associated
* programming language. Typically, refactorings which cause changes in elements other than the
* element which declares the refactored element should set this flag. This flag is used by
* language-specific tools to determine whether the refactoring may impact client code.
* </p>
*/
public static final int STRUCTURAL_CHANGE= 1 << 1;
/**
* Constant describing the user flag (value: <code>256</code>).
* <p>
* This constant is not intended to be used in refactoring descriptors. Clients should use the
* value of this constant to define user-defined flags with integer values greater than this
* constant. Clients must not use this constant directly.
* </p>
*/
public static final int USER_CHANGE= 1 << 8;
/** The comment of the refactoring, or <code>null</code> for no comment */
private String fComment;
/** The non-empty description of the refactoring */
private String fDescription;
/** The flags of the refactoring, or <code>NONE</code> */
private int fFlags;
/** The project name, or <code>null</code> for no project */
private String fProject;
/** The unique id of the refactoring */
private final String fRefactoringId;
/**
* The time stamp, or <code>-1</code> if no time information is associated with the refactoring
*/
private long fTimeStamp= -1;
////////////////////////////////////////////////////////////////////////////////
//CODINGSPECTATOR: The following attributes have been added for CodingSpectator.
////////////////////////////////////////////////////////////////////////////////
/**
* The existence of this attribute indicated that the descriptor has been generated by
* CodingSpectator. The value of this attribute is always "true".
*/
public static final String CAPTURED_BY_CODINGSPECTATOR_ATTRIBUTE= "captured-by-codingspectator"; //$NON-NLS-1$
/**
* The attribute for the code snippet that is captured during a refactoring.
*/
public static final String ATTRIBUTE_CODE_SNIPPET= "code-snippet"; //$NON-NLS-1$
/**
* The attribute for the selected text in a refactoring.
*/
public static final String ATTRIBUTE_SELECTION_TEXT= "selection-text"; //$NON-NLS-1$
/**
* The offset of the selected text within the code snippet that contains it.
*/
public static final String ATTRIBUTE_SELECTION_IN_CODE_SNIPPET= "selection-in-code-snippet"; //$NON-NLS-1$
/**
* The attribute for the status of a refactoring. This status contains the error and warning
* messages of the refactoring.
*/
public static final String ATTRIBUTE_STATUS= "status"; //$NON-NLS-1$
/**
* This attribute is to indicate whether a refactoring has been performed through a quick assist
* proposal.
*/
public static final String ATTRIBUTE_INVOKED_BY_QUICKASSIST= "invoked-by-quickassist"; //$NON-NLS-1$
/**
* This attribute is to indicate whether a refactoring has been performed through the structured
* selection i.e. Package View or Outline View.
*/
public static final String ATTRIBUTE_INVOKED_THROUGH_STRUCTURED_SELECTION= "invoked-through-structured-selection"; //$NON-NLS-1$
/**
* Creates a new refactoring descriptor.
*
* @param id the unique id of the refactoring
* @param project the non-empty name of the project associated with this refactoring, or
* <code>null</code> for a workspace refactoring
* @param description a non-empty human-readable description of the particular refactoring
* instance
* @param comment the human-readable comment of the particular refactoring instance, or
* <code>null</code> for no comment
* @param flags the flags of the refactoring descriptor
*/
protected RefactoringDescriptor(final String id, final String project, final String description, final String comment, final int flags) {
Assert.isNotNull(id);
Assert.isLegal(!"".equals(id), "Refactoring id must not be empty"); //$NON-NLS-1$ //$NON-NLS-2$
Assert.isLegal(project == null || !"".equals(project), "Project must either be null or non-empty"); //$NON-NLS-1$ //$NON-NLS-2$
Assert.isNotNull(description);
Assert.isLegal(!"".equals(description), "Description must not be empty"); //$NON-NLS-1$//$NON-NLS-2$
Assert.isLegal(flags >= NONE, "Flags must be non-negative"); //$NON-NLS-1$
fRefactoringId= id;
fDescription= description;
fProject= project;
fComment= comment;
fFlags= flags;
}
/**
* {@inheritDoc}
*/
public final int compareTo(final Object object) {
if (object instanceof RefactoringDescriptor) {
final RefactoringDescriptor descriptor= (RefactoringDescriptor)object;
final long delta= fTimeStamp - descriptor.fTimeStamp;
if (delta < 0)
return -1;
else if (delta > 0)
return +1;
}
return 0;
}
/**
* Creates the a new refactoring instance for this refactoring descriptor.
* <p>
* This method is used by the refactoring framework to instantiate a refactoring from a
* refactoring descriptor, in order to apply it later on a local or remote workspace.
* </p>
* <p>
* The returned refactoring must be in an initialized state, i.e. ready to be executed via
* {@link PerformRefactoringOperation}.
* </p>
*
* @param status a refactoring status used to describe the outcome of the initialization
* @return the refactoring, or <code>null</code> if this refactoring descriptor represents the
* unknown refactoring, or if no refactoring contribution is available for this
* refactoring descriptor which is capable to create a refactoring
* @throws CoreException if an error occurs while creating the refactoring instance
*/
public abstract Refactoring createRefactoring(RefactoringStatus status) throws CoreException;
/**
* {@inheritDoc}
*/
public final boolean equals(final Object object) {
if (object instanceof RefactoringDescriptor) {
final RefactoringDescriptor descriptor= (RefactoringDescriptor)object;
return fTimeStamp == descriptor.fTimeStamp && getDescription().equals(descriptor.getDescription());
}
return false;
}
/**
* Returns the details comment.
* <p>
* This information is used in the user interface to show additional details about the performed
* refactoring.
* </p>
*
* @return the details comment, or the empty string
*/
public final String getComment() {
return (fComment != null) ? fComment : ""; //$NON-NLS-1$
}
/**
* Returns the description.
* <p>
* This information is used to label a refactoring in the user interface.
* </p>
*
* @return the description
*/
public final String getDescription() {
return fDescription;
}
/**
* Returns the flags.
*
* @return the flags
*/
public final int getFlags() {
return fFlags;
}
/**
* Returns the refactoring id.
*
* @return the refactoring id.
*/
public final String getID() {
return fRefactoringId;
}
/**
* Returns the project name.
*
* @return the non-empty name of the project, or <code>null</code>
*/
public final String getProject() {
return fProject;
}
/**
* Returns the time stamp.
*
* @return the time stamp, or <code>-1</code> if no time information is available
*/
public final long getTimeStamp() {
return fTimeStamp;
}
/**
* {@inheritDoc}
*/
public final int hashCode() {
int code= getDescription().hashCode();
if (fTimeStamp >= 0)
code+= (17 * fTimeStamp);
return code;
}
/**
* Sets the details comment of this refactoring.
* <p>
* Note: This API must not be extended or reimplemented and should not be called from outside
* the refactoring framework.
* </p>
*
* @param comment the comment to set, or <code>null</code> for no comment
*/
public void setComment(final String comment) {
fComment= comment;
}
/**
* Sets the description of this refactoring.
* <p>
* Note: This API must not be extended or reimplemented and should not be called from outside
* the refactoring framework.
* </p>
*
* @param description the non-empty description of the refactoring to set
*
* @since 3.3
*/
public void setDescription(final String description) {
Assert.isNotNull(description);
Assert.isLegal(!"".equals(description), "Description must not be empty"); //$NON-NLS-1$ //$NON-NLS-2$
fDescription= description;
}
/**
* Sets the flags of this refactoring.
* <p>
* Note: This API must not be extended or reimplemented and should not be called from outside
* the refactoring framework.
* </p>
*
* @param flags the flags to set, or <code>NONE</code> to clear the flags
*
* @since 3.3
*/
public void setFlags(final int flags) {
Assert.isLegal(flags >= NONE, "Flags must be non-negative"); //$NON-NLS-1$
fFlags= flags;
}
/**
* Sets the project name of this refactoring.
* <p>
* Note: This API must not be extended or reimplemented and should not be called from outside
* the refactoring framework.
* </p>
*
* @param project the non-empty project name to set, or <code>null</code> for the workspace
*/
public void setProject(final String project) {
Assert.isLegal(project == null || !"".equals(project), "Project must either be null or non-empty"); //$NON-NLS-1$ //$NON-NLS-2$
fProject= project;
}
/**
* Sets the time stamp of this refactoring. This method can be called only once.
* <p>
* Note: This API must not be extended or reimplemented and should not be called from outside
* the refactoring framework.
* </p>
*
* @param stamp the time stamp to set
*/
public void setTimeStamp(final long stamp) {
Assert.isTrue(stamp >= 0);
fTimeStamp= stamp;
}
/**
* {@inheritDoc}
*/
public String toString() {
final StringBuffer buffer= new StringBuffer(128);
buffer.append(getClass().getName());
if (fRefactoringId.equals(ID_UNKNOWN))
buffer.append("[unknown refactoring]"); //$NON-NLS-1$
else {
buffer.append("[timeStamp="); //$NON-NLS-1$
buffer.append(fTimeStamp);
buffer.append(",id="); //$NON-NLS-1$
buffer.append(fRefactoringId);
buffer.append(",description="); //$NON-NLS-1$
buffer.append(fDescription);
buffer.append(",project="); //$NON-NLS-1$
buffer.append(fProject);
buffer.append(",comment="); //$NON-NLS-1$
buffer.append(fComment);
buffer.append(",flags="); //$NON-NLS-1$
buffer.append(fFlags);
buffer.append("]"); //$NON-NLS-1$
}
return buffer.toString();
}
/////////////////
//CODINGSPECTATOR
/////////////////
/**
* @param arguments A map of additional arguments
* @return A cloned version of this RefactoringDescriptor
*/
public RefactoringDescriptor cloneByAugmenting(Map arguments) {
return this;
}
}