/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* MetadataRenameUpdater.java
* Created: Apr 4, 2005
* By: Peter Cardwell
*/
package org.openquark.cal.metadata;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.openquark.cal.compiler.CodeAnalyser;
import org.openquark.cal.compiler.CodeQualificationMap;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleContainer;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleNameResolver;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.SourceIdentifier;
import org.openquark.cal.compiler.TypeChecker;
import org.openquark.cal.compiler.CodeAnalyser.QualificationResults;
import org.openquark.cal.compiler.CompilerMessage.Severity;
import org.openquark.cal.compiler.SourceIdentifier.Category;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.LocalizedResourceName;
import org.openquark.cal.services.ResourceName;
import org.openquark.cal.services.Status;
import org.openquark.cal.services.WorkspaceResource;
/**
* Provides functionality to update metadata resources to reflect a renaming.
*
* This class can be used to update metadata whenever a gem, type class, or type constructor
* is renamed.
*
* @author Peter Cardwell
*/
public class MetadataRenameUpdater {
/**
* Interface for listeners wishing to receive status updates from this updater.
* @author Peter Cardwell
*/
public static interface StatusListener {
/**
* Refactorer is about to use the specified resource.
* @param resource module to be accessed
*/
public void willUseResource(WorkspaceResource resource);
/**
* Refactorer has finished using the specified resource.
* @param resource module which has been accessed
*/
public void doneUsingResource(WorkspaceResource resource);
}
/**
* Used for errors that occur during the updateMetadata operation.
* @author Peter Cardwell
*/
private static class RenamingException extends Exception {
private static final long serialVersionUID = -6565271241591817292L;
private final String errorMessage;
RenamingException (String errorMessage) {
super(errorMessage);
this.errorMessage = errorMessage;
}
String getErrorMessage() {
return errorMessage;
}
}
private final Status status;
private final TypeChecker typeChecker;
private final QualifiedName newName;
private final QualifiedName oldName;
private final SourceIdentifier.Category category;
/** Status listener, if any */
private StatusListener statusListener = null;
/**
* Constructs a new MetadataRenameUpdater
* @param status A status object to keep track of any error messages.
* @param typeChecker The type checker to use for the renaming of code expressions (ie. in code gems).
* @param newName The new name of the identifier after it was renamed.
* @param oldName The old name of the identifier before it was renamed.
* @param category The category of the identifier.
*/
public MetadataRenameUpdater(Status status, TypeChecker typeChecker, QualifiedName newName, QualifiedName oldName, SourceIdentifier.Category category) {
this.status = status;
this.typeChecker = typeChecker;
this.newName = newName;
this.oldName = oldName;
this.category = category;
}
/**
* Returns the new CALFeatureName that the given metadata should be updated with, or
* if updating is not necessary, the old metadata's featureName.
*/
private CALFeatureName getNewMetadataFeatureName(CALFeatureMetadata metadata) {
// If we are renaming a module, we have to update the module name of metadata for all CALFeatures within that module.
if (category == SourceIdentifier.Category.MODULE_NAME) {
if (metadata.getFeatureName().getType() == CALFeatureName.MODULE) {
if (oldName.getUnqualifiedName().equals(metadata.getFeatureName().getName())) {
return CALFeatureName.getModuleFeatureName(newName.getModuleName());
}
} else {
QualifiedName oldMetadataName = QualifiedName.makeFromCompoundName(metadata.getFeatureName().getName());
if (oldName.getModuleName().equals(oldMetadataName.getModuleName())) {
QualifiedName newMetadataName = QualifiedName.make(newName.getModuleName(), oldMetadataName.getUnqualifiedName());
return new CALFeatureName(metadata.getFeatureName().getType(), newMetadataName.getQualifiedName());
}
}
// If we are not renaming a module, we only need to rename the metadata if is corresponds to the entity which we are renaming.
} else {
if (metadata.getFeatureName().getType() != CALFeatureName.MODULE) {
QualifiedName oldMetadataName = QualifiedName.makeFromCompoundName(metadata.getFeatureName().getName());
if (oldName.equals(oldMetadataName)) {
return new CALFeatureName(metadata.getFeatureName().getType(), newName.getQualifiedName());
}
}
}
return metadata.getFeatureName();
}
/**
* Updates the examples for the given metadata object to reflect the renaming.
* @return true if any examples were changed, false otherwise
*/
private boolean updateExamples(FunctionalAgentMetadata functionalAgentMetadata, ModuleContainer moduleContainer) {
// Get the array of examples and then loop through them and update them if necessary
CALExample[] examples = functionalAgentMetadata.getExamples();
boolean examplesChanged = false;
for (int i = 0, n = examples.length; i < n; i++) {
CALExample curExample = examples[i];
CALExpression oldExpression = curExample.getExpression();
// Get the qualified version of the new string
ModuleTypeInfo moduleTypeInfo = moduleContainer.getModuleTypeInfo(oldExpression.getModuleContext());
if (moduleTypeInfo == null) {
// The module is not in the workspace.
String featureName = functionalAgentMetadata.getFeatureName().getName();
ModuleName moduleContext = oldExpression.getModuleContext();
String errorMessage = "Can not update the metadata for " + featureName + ", example " + (i + 1) +
", since its module context " + moduleContext + " is not in the workspace.";
// Add a warning.
// Unfortunately, warnings are ignored.
// However, throwing a RenamingException will cause renaming to fail, which is not what we want.
status.add(new Status(Status.Severity.WARNING, errorMessage));
continue;
// throw new RenamingException(errorMessage);
}
ModuleNameResolver oldExpressionContextModuleNameResolver = moduleTypeInfo.getModuleNameResolver();
String newExpressionString = typeChecker.calculateUpdatedCodeExpression(oldExpression.getExpressionText(), oldExpression.getModuleContext(), oldExpressionContextModuleNameResolver, oldExpression.getQualificationMap(), oldName, newName, category, null);
CodeAnalyser codeAnalyser = new CodeAnalyser(typeChecker, moduleTypeInfo, false, false);
MessageLogger logger = new MessageLogger();
QualificationResults qualificationResults = codeAnalyser.qualifyExpression(newExpressionString, null, oldExpression.getQualificationMap(), logger);
if (qualificationResults == null) {
// failed to parse.
String featureName = functionalAgentMetadata.getFeatureName().getName();
String errorMessage =
"Can not update the metadata for " + featureName + ", example " + (i + 1) + ", since it could not be parsed.\n" +
"Compiler messages (if any): " + logger.getCompilerMessages(Severity.ERROR);
status.add(new Status(Status.Severity.WARNING, errorMessage));
continue;
}
String qualifiedNewExpressionString = qualificationResults.getQualifiedCode();
// Update the qualification map
CodeQualificationMap newQualifiedMap = oldExpression.getQualificationMap();
boolean qualificationMapWasUpdated = updateQualificationMap(newQualifiedMap);
boolean moduleContextNeedsUpdating =
(category == Category.MODULE_NAME && oldName.getModuleName().equals(oldExpression.getModuleContext()));
if (!newExpressionString.equals(oldExpression.getExpressionText()) || qualificationMapWasUpdated || moduleContextNeedsUpdating) {
// If changes were made, create a new expression and save the example.
ModuleName newModuleContext;
if (moduleContextNeedsUpdating) {
newModuleContext = newName.getModuleName();
} else {
newModuleContext = oldExpression.getModuleContext();
}
CALExpression newExpression = new CALExpression(newModuleContext, newExpressionString, newQualifiedMap, qualifiedNewExpressionString);
CALExample newExample = new CALExample(newExpression, curExample.getDescription(), curExample.evaluateExample());
examples[i] = newExample;
examplesChanged = true;
}
}
if (examplesChanged) {
// Replace the metadata's examples with the new contents of the examples array and saave the metadata
functionalAgentMetadata.setExamples(examples);
return true;
}
return false;
}
/**
* Updates all the metadata files stored by the given workspace to reflect the renaming.
* @param moduleContainer The workspace containing all the metadatas the user wishes to update
* @return True if any metadata was updated
*/
public boolean updateMetadata(ModuleContainer moduleContainer) {
List<CALFeatureMetadata> undoList = new ArrayList<CALFeatureMetadata>(); // A list of metadata files that have been updated and should be undone if an error is encountered.
try {
ModuleName[] workspaceModuleNames = moduleContainer.getModuleNames();
// loop through each module in the workspace
for (final ModuleName moduleName : workspaceModuleNames) {
MetadataManager metadataManager = (MetadataManager) moduleContainer.getResourceManager(moduleName, WorkspaceResource.METADATA_RESOURCE_TYPE);
// Loop over all metadata resources for the module in the workspace and get their corresponding metadata file.
for (Iterator<WorkspaceResource> it = ((MetadataStore)metadataManager.getResourceStore()).getResourceIterator(moduleName); it.hasNext(); ) {
boolean metadataUpdated = false;
WorkspaceResource metadataResource = it.next();
if (statusListener != null) {
statusListener.willUseResource(metadataResource);
}
ResourceName oldMetadataResourceName = metadataResource.getIdentifier().getResourceName();
Locale metadataLocale = LocalizedResourceName.localeOf(oldMetadataResourceName);
// We want to keep a copy of the old metadata without any changes made to it so we can restore it later if an error occurs.
CALFeatureMetadata oldMetadata = metadataManager.getMetadata((CALFeatureName)oldMetadataResourceName.getFeatureName(), metadataLocale);
// Make sure the metadata's module is in the current workspace
ModuleName metadataModuleName = oldMetadata.getFeatureName().toModuleName();
if (moduleContainer.containsModule(metadataModuleName)) {
continue;
}
CALFeatureName newMetadataFeatureName = getNewMetadataFeatureName(oldMetadata);
CALFeatureMetadata newMetadata = oldMetadata.copy(newMetadataFeatureName, metadataLocale);
if (!newMetadataFeatureName.equals(oldMetadata.getFeatureName())) {
metadataUpdated = true;
}
// Try to update the examples if the metadata is for a functional agent.
if (newMetadata instanceof FunctionalAgentMetadata) {
if (updateExamples((FunctionalAgentMetadata)newMetadata, moduleContainer)) {
metadataUpdated = true;
}
}
if (metadataUpdated) {
if (!metadataManager.getResourceStore().isWriteable(oldMetadataResourceName)) {
throw new RenamingException("Can not update the metadata for " + newMetadata.getFeatureName().getName() + " because it is not writeable.");
}
Status saveMetadataStatus = new Status("Save metadata status");
metadataManager.saveMetadata(newMetadata, oldMetadata.getFeatureName(), metadataLocale, saveMetadataStatus);
if (saveMetadataStatus.getSeverity().equals(Status.Severity.ERROR)) {
throw new RenamingException("Error saving the updated metadata for " + newMetadata.getFeatureName().getName() + ".");
}
undoList.add(oldMetadata);
}
if (statusListener != null) {
statusListener.doneUsingResource(metadataResource);
}
}
}
// Return true if the undoList has contents (ie. changes have been made)
return !undoList.isEmpty();
} catch (RenamingException e) {
status.add(new Status(Status.Severity.ERROR, e.getErrorMessage()));
for (int i = undoList.size() - 1; i >= 0; i--) {
CALFeatureMetadata metadata = undoList.get(i);
Status undoStatus = new Status("Undo metadata update status");
moduleContainer.saveMetadata(metadata, undoStatus);
if (undoStatus.getSeverity().equals(Status.Severity.ERROR)) {
String msg = "FATAL: Error undoing the metadata update for " + metadata.getDisplayName();
status.add(new Status(Status.Severity.ERROR, msg));
}
}
return false;
}
}
/**
* Updates all references to the renamed entity in the given CodeQualificationMap. If the entity type is a module,
* this can include any identifiers that use that module name. Otherwise, the qualification for the entity itself will be
* updated if it is in the map.
* @param qualificationMap The map to update
* @return True if the qualification map is changed.
*/
private boolean updateQualificationMap(CodeQualificationMap qualificationMap) {
boolean changesMade = false;
SourceIdentifier.Category[] categories = new SourceIdentifier.Category[] {
SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD,
SourceIdentifier.Category.DATA_CONSTRUCTOR,
SourceIdentifier.Category.TYPE_CONSTRUCTOR,
SourceIdentifier.Category.TYPE_CLASS
};
for (final Category sectionCategory : categories) {
Set<String> unqualifiedNames = qualificationMap.getUnqualifiedNames(sectionCategory);
for (final String unqualifiedName : unqualifiedNames) {
QualifiedName qualifiedName = qualificationMap.getQualifiedName(unqualifiedName, sectionCategory);
if (category == sectionCategory && oldName.equals(qualifiedName)) {
qualificationMap.removeQualification(unqualifiedName, sectionCategory);
qualificationMap.putQualification(newName.getUnqualifiedName(), newName.getModuleName(), sectionCategory);
changesMade = true;
} else if (category == SourceIdentifier.Category.MODULE_NAME && oldName.getModuleName().equals(qualifiedName.getModuleName())) {
qualificationMap.removeQualification(unqualifiedName, sectionCategory);
qualificationMap.putQualification(unqualifiedName, newName.getModuleName(), sectionCategory);
changesMade = true;
}
}
}
return changesMade;
}
/** Set status listener to use */
public void setStatusListener(StatusListener statusListener) {
this.statusListener = statusListener;
}
}