/*******************************************************************************
* Copyright (c) 2011 Gerd Wuetherich (gerd@gerd-wuetherich.de).
* 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:
* Gerd Wuetherich (gerd@gerd-wuetherich.de) - initial API and implementation
******************************************************************************/
package org.bundlemaker.core.internal.modules.modularizedsystem;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.bundlemaker.core.common.collections.GenericCache;
import org.bundlemaker.core.internal.api.resource.IModifiableModularizedSystem;
import org.bundlemaker.core.internal.api.resource.IModifiableModule;
import org.bundlemaker.core.internal.modules.ChangeAction;
import org.bundlemaker.core.internal.modules.Group;
import org.bundlemaker.core.internal.modules.Module;
import org.bundlemaker.core.internal.resource.ModuleIdentifier;
import org.bundlemaker.core.internal.resource.Resource;
import org.bundlemaker.core.internal.transformation.BasicProjectContentTransformation;
import org.bundlemaker.core.internal.transformation.IInternalTransformation;
import org.bundlemaker.core.internal.transformation.IUndoableTransformation;
import org.bundlemaker.core.project.IMovableUnit;
import org.bundlemaker.core.project.IProjectContentResource;
import org.bundlemaker.core.resource.IModule;
import org.bundlemaker.core.resource.IModuleAwareBundleMakerProject;
import org.bundlemaker.core.resource.IModuleIdentifier;
import org.bundlemaker.core.resource.IModuleResource;
import org.bundlemaker.core.resource.ITransformation;
import org.bundlemaker.core.spi.modext.ICacheCallback;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
/**
* <p>
* </p>
*
* @author Gerd Wütherich (gerd@gerd-wuetherich.de)
*/
public abstract class AbstractTransformationAwareModularizedSystem extends AbstractModularizedSystem {
/** resource -> resource module */
private GenericCache<IModuleResource, Set<IModule>> _resourceToResourceModuleCache;
/** - */
private List<ICacheCallback> _cacheCallbacks;
/**
* <p>
* Creates a new instance of type {@link AbstractTransformationAwareModularizedSystem}.
* </p>
*
* @param name
* @param project
*/
public AbstractTransformationAwareModularizedSystem(String name, IModuleAwareBundleMakerProject project) {
super(name, project);
//
_cacheCallbacks = new CopyOnWriteArrayList<ICacheCallback>();
}
/**
* {@inheritDoc}
*/
@Override
public void registerCacheCallback(ICacheCallback cacheCallback) {
if (!_cacheCallbacks.contains(cacheCallback)) {
_cacheCallbacks.add(cacheCallback);
}
}
/**
* {@inheritDoc}
*/
@Override
public void unregisterCacheCallback(ICacheCallback cacheCallback) {
_cacheCallbacks.remove(cacheCallback);
}
/**
* {@inheritDoc}
*/
@Override
public void applyTransformations(IProgressMonitor progressMonitor, List<ITransformation> transformations) {
//
Assert.isNotNull(transformations);
//
if (progressMonitor == null) {
progressMonitor = new NullProgressMonitor();
}
SubMonitor subMonitor = SubMonitor.convert(progressMonitor);
subMonitor.beginTask("Transforming Module '" + getName() + "'", 100);
_applyTransformations(subMonitor, transformations.toArray(new ITransformation[0]));
}
/**
* {@inheritDoc}
*/
@Override
public void undoTransformations(IProgressMonitor progressMonitor) {
undoUntilTransformation(progressMonitor, null);
}
/*
* (non-Javadoc)
*
* @see
* org.bundlemaker.core.modules.IModularizedSystem#undoUntilTransformation(org.eclipse.core.runtime.IProgressMonitor,
* org.bundlemaker.core.transformation.ITransformation)
*/
@Override
public void undoUntilTransformation(IProgressMonitor progressMonitor, ITransformation toTransformation) {
//
boolean disableModelModifiedNotification = getListenerList().isModelModifiedNotificationDisabled();
try {
//
getListenerList().disableModelModifiedNotification(true);
//
for (ITransformation transformation : getTransformations()) {
if (!(transformation instanceof IUndoableTransformation)) {
throw new RuntimeException("TODO");
}
}
// We have to undo the transformations in reverse order
List<ITransformation> transformationList = getModifiableTransformationList();
while (!transformationList.isEmpty()) {
// Get last transformation
IUndoableTransformation undoableTransformation = (IUndoableTransformation) transformationList
.get(transformationList.size() - 1);
// check
if (toTransformation != null && toTransformation.equals(undoableTransformation)) {
break;
}
// undo transformation
undoableTransformation.undo();
// remove from transformation list
transformationList.remove(undoableTransformation);
}
} finally {
//
getListenerList().disableModelModifiedNotification(disableModelModifiedNotification);
}
}
/**
* {@inheritDoc}
*/
@Override
public void undoLastTransformation() {
// get the last transformation
ITransformation lastTransformation = getTransformations().get(getTransformations().size() - 1);
// check if we have an undoable transformation
if (!(lastTransformation instanceof IUndoableTransformation)) {
throw new RuntimeException("TODO");
}
// remove transformation...
getModifiableTransformationList().remove(getTransformations().size() - 1);
// ...undo transformation
IUndoableTransformation undoableTransformation = (IUndoableTransformation) lastTransformation;
undoableTransformation.undo();
}
/**
* {@inheritDoc}
*/
@Override
public void applyTransformations(IProgressMonitor progressMonitor, ITransformation... transformations) {
//
if (progressMonitor == null) {
progressMonitor = new NullProgressMonitor();
}
SubMonitor subMonitor = SubMonitor.convert(progressMonitor);
subMonitor.beginTask("Transforming Module '" + getName() + "'", 100);
_applyTransformations(subMonitor, transformations);
}
public void initialize(IProgressMonitor progressMonitor) {
//
if (progressMonitor == null) {
progressMonitor = new NullProgressMonitor();
}
SubMonitor subMonitor = SubMonitor.convert(progressMonitor);
subMonitor.beginTask("Transforming Module '" + getName() + "'", 100);
// step 1: clear prior results
getModifiableResourceModules().clear();
preApplyTransformations();
subMonitor.worked(20);
}
/**
* <p>
* </p>
*
* @param subMonitor
*/
private void _applyTransformations(SubMonitor subMonitor, ITransformation... transformations) {
// step 4: transform modules
SubMonitor transformationMonitor = subMonitor.newChild(70);
transformationMonitor.beginTask("Begin", transformations.length * 4);
for (ITransformation transformation : transformations) {
// step 4.1: apply transformation
((IInternalTransformation) transformation).apply((IModifiableModularizedSystem) this,
transformationMonitor.newChild(1));
//
if (!(transformation instanceof BasicProjectContentTransformation)) {
if (!getModifiableTransformationList().contains(transformation)) {
//
getModifiableTransformationList().add(transformation);
}
}
//
transformationMonitor.worked(1);
}
afterApplyTransformations();
}
protected void afterApplyTransformations() {
//
}
/**
* <p>
* </p>
*
* @param path
* @return
*/
public Group getOrCreateGroup(IPath path) {
Assert.isNotNull(path);
Assert.isTrue(!path.isEmpty(), "Path must not be emtpy.");
//
Group group = getGroup(path);
if (group != null) {
return group;
}
//
if (path.segmentCount() == 1) {
Group result = new Group(path.lastSegment(), null, this);
internalGroups().add(result);
Assert.isNotNull(result);
//
getListenerList().fireGroupChanged(result, ChangeAction.ADDED);
return result;
} else {
Group parent = getOrCreateGroup(path.removeLastSegments(1));
Group result = new Group(path.lastSegment(), parent, this);
internalGroups().add(result);
Assert.isNotNull(result);
//
getListenerList().fireGroupChanged(result, ChangeAction.ADDED);
return result;
}
}
/**
* <p>
* </p>
*
* @param path
* @return
*/
@Override
public void removeGroup(Group group) {
Assert.isNotNull(group);
internalGroups().remove(group);
Assert.isNotNull(group);
//
getListenerList().fireGroupChanged(group, ChangeAction.REMOVED);
}
@Override
public void removeGroup(IPath path) {
Assert.isNotNull(path);
Group group = getGroup(path);
if (group == null) {
// TODO
throw new RuntimeException(String.format("Group '%s' does not exist.", group));
}
removeGroup(group);
}
/**
* <p>
* </p>
*
* @param path
* @return
*/
public Group getGroup(IPath path) {
// We can not use a hash map here, because it is possible to change the path of a group (which would be the key in
// the map). So we have to iterate over all groups and find the right one...
for (Group group : internalGroups()) {
if (group.getPath().equals(path)) {
return group;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public IModifiableModule createResourceModule(IModuleIdentifier createModuleIdentifier) {
// create the result
Module resourceModule = new Module(createModuleIdentifier, this);
// add it to the internal hash map
getModifiableResourceModules().add(resourceModule);
// notify
resourceModuleAdded(resourceModule);
// return the result
return resourceModule;
}
@Override
public IModifiableModule createResourceModule(ModuleIdentifier moduleIdentifier, IPath path) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("rawtypes")
@Override
public void addModule(IModule module) {
Assert.isNotNull(module);
if (module instanceof IModifiableModule) {
//
Assert.isTrue(!hasResourceModule(module.getModuleIdentifier()));
//
IModifiableModule resourceModule = (IModifiableModule) module;
//
((Module) resourceModule).attach(this);
getModifiableResourceModules().add(resourceModule);
// notify
resourceModuleAdded(resourceModule);
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeModule(IModuleIdentifier identifier) {
Assert.isNotNull(identifier);
if (hasResourceModule(identifier)) {
// remove the entry
Module resourceModule = (Module) getModule(identifier);
getModifiableResourceModules().remove(resourceModule);
resourceModule.detach();
// notify
resourceModuleRemoved((IModifiableModule) resourceModule);
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeModule(IModule module) {
Assert.isNotNull(module);
// remove the module
removeModule(module.getModuleIdentifier());
}
/**
* <p>
* </p>
*
* @return
*/
protected final GenericCache<IModuleResource, Set<IModule>> getResourceToResourceModuleCache() {
//
if (_resourceToResourceModuleCache == null) {
// create _resourceToResourceModuleCache
_resourceToResourceModuleCache = new GenericCache<IModuleResource, Set<IModule>>() {
@Override
protected Set<IModule> create(IModuleResource resource) {
return new HashSet<IModule>();
}
};
}
//
return _resourceToResourceModuleCache;
}
/**
* {@inheritDoc}
*/
protected void preApplyTransformations() {
// clear all the caches
getResourceToResourceModuleCache().clear();
//
for (ICacheCallback cacheCallback : _cacheCallbacks) {
cacheCallback.clearCaches();
}
}
/**
* <p>
* </p>
*
* @param resource
* @param resourceModule
* @param action
*/
public void movableUnitChanged(IMovableUnit movableUnit, IModule resourceModule, ChangeAction action) {
for (IProjectContentResource moduleResource : movableUnit.getAssociatedBinaryResources()) {
internalResourceChanged(moduleResource.adaptAs(IModuleResource.class), resourceModule, action);
}
if (movableUnit.hasAssociatedSourceResource()) {
internalResourceChanged(movableUnit.getAssociatedSourceResource().adaptAs(IModuleResource.class), resourceModule,
action);
}
switch (action) {
case ADDED: {
for (ICacheCallback cacheCallback : _cacheCallbacks) {
cacheCallback.movableUnitAdded(movableUnit, resourceModule);
}
break;
}
case REMOVED: {
for (ICacheCallback cacheCallback : _cacheCallbacks) {
cacheCallback.movableUnitRemoved(movableUnit, resourceModule);
}
break;
}
}
}
/**
* {@inheritDoc}
*/
protected void resourceModuleAdded(IModifiableModule resourceModule) {
Assert.isNotNull(resourceModule);
//
getListenerList().fireModuleChanged(resourceModule, ChangeAction.ADDED);
//
for (IMovableUnit movableUnit : resourceModule.getMovableUnits()) {
movableUnitChanged(movableUnit, resourceModule, ChangeAction.ADDED);
}
}
/**
* {@inheritDoc}
*/
protected void resourceModuleRemoved(IModifiableModule resourceModule) {
Assert.isNotNull(resourceModule);
//
for (IMovableUnit movableUnit : resourceModule.getMovableUnits()) {
movableUnitChanged(movableUnit, resourceModule, ChangeAction.REMOVED);
}
//
getListenerList().fireModuleChanged(resourceModule, ChangeAction.REMOVED);
}
/**
* <p>
* </p>
*
* @param resource
* @return
*/
public IModule getAssociatedResourceModule(IModuleResource resource) {
Assert.isNotNull(resource);
if (resource instanceof Resource) {
resource = ((Resource) resource).getResourceStandin();
}
//
Set<IModule> resourceModules = _resourceToResourceModuleCache.get(resource);
//
if (resourceModules == null || resourceModules.isEmpty()) {
return null;
} else if (resourceModules.size() > 1) {
throw new RuntimeException(String.format("Resource '%s' is contained in multiple ResourceModules: %s.", resource,
resourceModules));
} else {
return resourceModules.toArray(new IModule[0])[0];
}
}
private void internalResourceChanged(IModuleResource resource, IModule resourceModule, ChangeAction action) {
// step 1: add/remove to resource map
switch (action) {
case ADDED: {
_resourceToResourceModuleCache.getOrCreate(resource).add(resourceModule);
break;
}
case REMOVED: {
Set<IModule> resourceModules = _resourceToResourceModuleCache.get(resource);
if (resourceModules != null) {
resourceModules.remove(resourceModule);
if (resourceModules.isEmpty()) {
_resourceToResourceModuleCache.remove(resource);
}
}
break;
}
}
}
}