package net.sourceforge.c4jplugin.internal.core;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import net.sourceforge.c4jplugin.C4JActivator;
import net.sourceforge.c4jplugin.internal.exceptions.OldContractModelException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.ui.IMemento;
public class ContractReferenceModel {
static private final String MODEL_VERSION = "0.2.0";
// session properties
static private final QualifiedName QN_CONTRACTED_PROPERTY = new QualifiedName(C4JActivator.PLUGIN_ID, "isContracted");
static private final QualifiedName QN_CONTRACT_PROPERTY = new QualifiedName(C4JActivator.PLUGIN_ID, "isContract");
static private final QualifiedName QN_DIRECTCONTRACT_PROPERTY = new QualifiedName(C4JActivator.PLUGIN_ID, "directContract");
static private final QualifiedName QN_TARGET_PROPERTY = new QualifiedName(C4JActivator.PLUGIN_ID, "target");
static private ConcurrentHashMap<IResource, Collection<IResource>> mapClassToContracts = new ConcurrentHashMap<IResource, Collection<IResource>>();
//static private ConcurrentHashMap<IResource, Collection<IResource>> mapContractToClasses = new ConcurrentHashMap<IResource, Collection<IResource>>();
//static private ConcurrentHashMap<IResource, IResource> mapTargetToContract = new ConcurrentHashMap<IResource, IResource>();
//static private ConcurrentHashMap<IResource, IResource> mapContractToTarget = new ConcurrentHashMap<IResource, IResource>();
static private IResourceVisitor clearSessionProperties = new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
if (resource.getName().endsWith(".java"))
clearSessionProperties(resource);
return true;
}
};
private ContractReferenceModel() {
}
synchronized static public void loadModel(final IMemento memento) throws CoreException, OldContractModelException {
clearModel();
final IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
String rootID = memento.getID();
if (rootID == null) rootID = "0.1.0";
if (rootID.compareTo(MODEL_VERSION) < 0)
throw new OldContractModelException(CoreMessages.ContractReferenceModel_oldModel, rootID);
IMemento[] children = memento.getChildren("contractedClass");
if (children == null) return;
for (IMemento child : children) {
String classPath = child.getID();
IResource resource = wsRoot.findMember(new Path(classPath));
if (resource != null && resource.exists()) {
loadDirectContract(resource, child, wsRoot);
loadSuperContracts(resource, child, wsRoot);
}
}
// setting the contracted state of all other resources to false
wsRoot.accept(new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
if (resource.getName().endsWith(".java")) {
if (resource.getSessionProperty(QN_CONTRACTED_PROPERTY) == null) {
resource.setSessionProperty(QN_CONTRACTED_PROPERTY, false);
}
return false;
}
return true;
}
});
}
private static void loadDirectContract(IResource target, IMemento memento, IWorkspaceRoot wsRoot)
throws CoreException {
IMemento mDirectContract = memento.getChild("directContract");
if (mDirectContract != null) {
String directContract = mDirectContract.getID();
if (directContract != null) {
IResource resourceDirectContract = wsRoot.findMember(new Path(directContract));
if (resourceDirectContract != null && resourceDirectContract.exists()) {
target.setSessionProperty(QN_CONTRACTED_PROPERTY, true);
target.setSessionProperty(QN_DIRECTCONTRACT_PROPERTY, resourceDirectContract);
resourceDirectContract.setSessionProperty(QN_CONTRACT_PROPERTY, true);
resourceDirectContract.setSessionProperty(QN_TARGET_PROPERTY, target);
Collection<IResource> contracts = mapClassToContracts.get(target);
if (contracts == null) {
contracts = new HashSet<IResource>();
mapClassToContracts.put(target, contracts);
}
contracts.add(resourceDirectContract);
}
}
}
}
private static void loadSuperContracts(IResource target, IMemento memento, IWorkspaceRoot wsRoot)
throws CoreException {
IMemento[] children = memento.getChildren("superContract");
if (children == null || children.length == 0) return;
Collection<IResource> setContracts = new HashSet<IResource>();
for (IMemento child : children) {
String superContract = child.getID();
IResource resourceSuperContract = wsRoot.findMember(new Path(superContract));
if (resourceSuperContract != null && resourceSuperContract.exists()) {
setContracts.add(resourceSuperContract);
resourceSuperContract.setSessionProperty(QN_CONTRACT_PROPERTY, true);
}
}
if (setContracts.size() > 0) {
target.setSessionProperty(QN_CONTRACTED_PROPERTY, true);
Collection<IResource> contracts = mapClassToContracts.get(target);
if (contracts == null) {
contracts = new HashSet<IResource>();
mapClassToContracts.put(target, contracts);
}
contracts.addAll(setContracts);
}
}
synchronized static public void saveModel(IMemento memento) {
memento.putString(IMemento.TAG_ID, MODEL_VERSION);
for (IResource target : mapClassToContracts.keySet()) {
IMemento mTarget = memento.createChild("contractedClass");
mTarget.putString(IMemento.TAG_ID, target.getFullPath().toString());
IResource directContract = getDirectContract(target);
if (directContract != null) {
IMemento mDirectContract = mTarget.createChild("directContract");
mDirectContract.putString(IMemento.TAG_ID, directContract.getFullPath().toString());
}
Collection<IResource> contracts = mapClassToContracts.get(target);
if (contracts != null) {
for (IResource contract : contracts) {
if (directContract != null && contract.equals(directContract)) continue;
IMemento mSuperContract = mTarget.createChild("superContract");
mSuperContract.putString(IMemento.TAG_ID, contract.getFullPath().toString());
}
}
}
}
/**
* Completely resets the model.
*
*/
synchronized static public void clearModel() {
mapClassToContracts.clear();
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (IProject project : projects) {
try {
if (project.isOpen())
project.accept(clearSessionProperties);
} catch (CoreException e) {
e.printStackTrace();
}
}
}
/**
* Clears the project specific model data. This may leave projects
* which depend on <em>project</em> in an inconsistent state. It is
* the responsibility of the caller to track down dependencies.
*
* @param project
*/
synchronized static public void clearModel(IProject project) {
for (IResource resource : mapClassToContracts.keySet()) {
if (resource.getProject().equals(project)) {
mapClassToContracts.remove(resource);
}
}
for (Collection<IResource> references : mapClassToContracts.values()) {
Iterator<IResource> iter = references.iterator();
while (iter.hasNext()) {
IResource reference = iter.next();
if (reference.getProject().equals(project))
iter.remove();
}
}
try {
project.accept(clearSessionProperties);
} catch (CoreException e) {
e.printStackTrace();
}
}
synchronized static private void clearSessionProperties(IResource resource) {
clearSessionProperty(resource, QN_CONTRACTED_PROPERTY);
clearSessionProperty(resource, QN_CONTRACT_PROPERTY);
clearSessionProperty(resource, QN_TARGET_PROPERTY);
clearSessionProperty(resource, QN_DIRECTCONTRACT_PROPERTY);
}
synchronized static private void clearSessionProperty(IResource resource, QualifiedName property) {
try {
resource.setSessionProperty(property, null);
}
catch (CoreException e) {}
}
synchronized static public IResource getDirectContract(IResource target) {
if (target == null) return null;
try {
IResource direct = (IResource)target.getSessionProperty(QN_DIRECTCONTRACT_PROPERTY);
return direct;
} catch (CoreException e) {
//e.printStackTrace();
return null;
}
}
synchronized static public Boolean isContracted(IResource resource, boolean checkAgainstModel) {
if (checkAgainstModel) {
return mapClassToContracts.containsKey(resource);
}
return isContracted(resource);
}
synchronized static public Boolean isContracted(IResource resource) {
if (resource == null) return false;
Boolean contracted = null;
try {
contracted = (Boolean)resource.getSessionProperty(QN_CONTRACTED_PROPERTY);
} catch (CoreException e) {}
return contracted;
}
synchronized static public Boolean isTarget(IResource resource) {
IResource directContract = getDirectContract(resource);
return directContract != null;
}
/**
* Returns true if <em>resource</em> is a contract. Calling
* <em>checkAgainstModel = false</em> is the same as calling
* <em>isContract(resource)</em> and is usuallay a lot faster
* than using true.
*
* @param resource
* @param checkAgainstModel
* @return
*/
synchronized static public boolean isContract(IResource resource, boolean checkAgainstModel) {
if (checkAgainstModel) {
for (Collection<IResource> contracts : mapClassToContracts.values()) {
if (contracts.contains(resource)) return true;
}
return false;
}
return isContract(resource);
}
synchronized static public boolean isContract(IResource resource) {
Object contract = null;
try {
contract = resource.getSessionProperty(QN_CONTRACT_PROPERTY);
} catch (CoreException e) {}
if (contract == null) return false;
return (Boolean)contract;
}
/**
* Removes the contract <em>contract</em> from the model. This may leave
* the classes which have been contracted by this contract in an inconsistent
* state. Use the returned collection to update the affected classes.
*
* @param contract
* @return All classes which where contracted by the removed contract
* (includes subtypes as well)
*/
synchronized static public Collection<IResource> removeContract(IResource contract) {
Collection<IResource> contractedClasses = getContractedClasses(contract);
for (IResource contractedClass : contractedClasses) {
Collection<IResource> contracts = mapClassToContracts.get(contractedClass);
if (contracts.remove(contract)) {
IResource directContract = getDirectContract(contractedClass);
if (directContract != null && directContract.equals(contract)) {
clearSessionProperty(contractedClass, QN_DIRECTCONTRACT_PROPERTY);
}
if (contracts.size() == 0)
clearSessionProperty(contractedClass, QN_CONTRACTED_PROPERTY);
}
}
clearSessionProperties(contract);
return contractedClasses;
}
/**
* Removes the contracted class from the model. This may leave its
* contracts and subtypes in an inconsistent state.
*
* @param resource
* @return Returns the contracts which guarded this class.
*/
synchronized static public Collection<IResource> removeContractedClass(IResource resource) {
Collection<IResource> contracts = getContracts(resource);
for (IResource contract : contracts) {
IResource target = getTarget(contract);
if (target.equals(resource))
clearSessionProperties(contract);
}
mapClassToContracts.remove(resource);
clearSessionProperties(resource);
return contracts;
}
/**
* The caller should update the state of the subtypes of <em>resource</em>
* and the added super-contracts.
*
* @param resource
* @param contracts
* @return
*/
synchronized static public void addSuperContracts(IResource resource, Collection<IResource> contracts) {
addContracts(resource, contracts);
}
/**
* The caller should update the state of the subtypes of <em>target</em>
* and the added direct contract.
*
* @param target
* @param contract
*/
synchronized static public void addDirectContract(IResource target, IResource contract) {
try {
contract.setSessionProperty(QN_TARGET_PROPERTY, target);
target.setSessionProperty(QN_DIRECTCONTRACT_PROPERTY, contract);
Collection<IResource> contracts = new Vector<IResource>();
contracts.add(contract);
addContracts(target, contracts);
} catch (CoreException e) {}
}
synchronized static private void addContracts(IResource resource, Collection<IResource> contracts) {
if (contracts == null || contracts.size() == 0) return;
try {
for (IResource contract : contracts)
contract.setSessionProperty(QN_CONTRACT_PROPERTY, true);
} catch (CoreException e) {}
Collection<IResource> allContracts = mapClassToContracts.get(resource);
try {
resource.setSessionProperty(QN_CONTRACTED_PROPERTY, true);
if (allContracts == null) {
allContracts = new HashSet<IResource>();
mapClassToContracts.put(resource, allContracts);
}
allContracts.addAll(contracts);
} catch (CoreException e) {}
}
/**
* Returns all contracts for <em>resource</em>
*
* @param resource
* @return
*/
synchronized static public Collection<IResource> getContracts(IResource resource) {
Collection<IResource> contractReferences = mapClassToContracts.get(resource);
if (contractReferences == null)
return Collections.emptyList();
return Collections.unmodifiableCollection(contractReferences);
}
/**
* Returns all classes which are contracted by <em>contract</em>
* @param contract
* @return
*/
synchronized static public Collection<IResource> getContractedClasses(IResource contract) {
Collection<IResource> contractedClasses = new HashSet<IResource>();
for (IResource contractedClass : mapClassToContracts.keySet()) {
Collection<IResource> contracts = mapClassToContracts.get(contractedClass);
if (contracts.contains(contract))
contractedClasses.add(contractedClass);
}
return Collections.unmodifiableCollection(contractedClasses);
}
/**
* Returns the target for the given contract, i. e. the class which
* directly references this contract.
*
* @param contract
* @return The target or null in case something went wrong
*/
synchronized static public IResource getTarget(IResource contract) {
if (contract == null) return null;
try {
IResource target = (IResource)contract.getSessionProperty(QN_TARGET_PROPERTY);
return target;
} catch (CoreException e) {}
return null;
}
synchronized static public Collection<IResource> getAllContracts() {
Collection<IResource> allContracts = new HashSet<IResource>();
for (Collection<IResource> contracts : mapClassToContracts.values()) {
allContracts.addAll(contracts);
}
return Collections.unmodifiableCollection(allContracts);
}
synchronized static public Collection<IResource> getAllContractedClasses() {
return Collections.unmodifiableCollection(mapClassToContracts.keySet());
}
}