package de.hub.emffrag.fragmentation;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMLParserPoolImpl;
import org.junit.Assert;
import com.google.common.base.Throwables;
import de.hub.emffrag.EmfFragActivator;
import de.hub.emffrag.datastore.DataStoreURIHandler;
import de.hub.emffrag.datastore.IDataMap;
import de.hub.emffrag.datastore.IDataStore;
import de.hub.emffrag.datastore.KeyType;
import de.hub.emffrag.datastore.LongKeyType;
import de.hub.emffrag.model.emffrag.EmfFragFactory;
import de.hub.emffrag.model.emffrag.EmfFragPackage;
import de.hub.emffrag.model.emffrag.Root;
public class FragmentedModel extends ResourceImpl {
public static final String FRAGMENTS_INDEX_PREFIX = "f";
public static final String ID_INDEX_PREFIX = "c";
public static final String INDEX_CLASSES_PREFIX = "i";
public static final String INDEX_FEATURES_PREFIX = "j";
private final static XMLParserPoolImpl xmlParserPool = new XMLParserPoolImpl(true);
private final static Map<Object, Object> options = new HashMap<Object, Object>();
static {
options.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.FALSE);
options.put(XMLResource.OPTION_ENCODING, "UTF-8");
options.put(XMLResource.OPTION_USE_PARSER_POOL, xmlParserPool);
HashMap<String, Boolean> parserFeatures = new HashMap<String, Boolean>();
parserFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
options.put(XMLResource.OPTION_PARSER_FEATURES, parserFeatures);
}
private final ResourceSet resourceSet;
private final FragmentsCache fragmentCache;
private final IDataStore dataStore;
private final IDataMap<Long> fragmentIndex;
private final IdIndex idIndex;
private final Statistics statistics = new Statistics();
private final Fragment rootFragment;
public FragmentedModel(IDataStore dataStore) {
this(dataStore, -1);
}
public FragmentedModel(IDataStore dataStore, int cacheSize) {
super(dataStore.getURI());
ReflectiveMetaModelRegistry.instance.registerUserMetaModel(EmfFragPackage.eINSTANCE);
this.dataStore = dataStore;
if (cacheSize == -1) {
cacheSize = EmfFragActivator.instance.cacheSize;
}
fragmentCache = new FragmentsCache(cacheSize) {
@Override
protected void onEvict(Fragment fragment) {
unloadFragment(fragment);
}
};
this.fragmentIndex = dataStore.getMap((FRAGMENTS_INDEX_PREFIX + "_").getBytes(), LongKeyType.instance) ;
this.idIndex = new IdIndex(dataStore.getMap((ID_INDEX_PREFIX + "_").getBytes(), LongKeyType.instance));
resourceSet = createAndConfigureAResourceSet(dataStore);
Long first = fragmentIndex.first();
if (first == null) {
rootFragment = createFragment(null, null);
} else {
rootFragment = (Fragment) resourceSet.getResource(fragmentIndex.getURI(first), true);
for (EObject object: rootFragment.getContents()) {
// add via doAddUnique to prevent inserve add which is for users and would produce duplication of the internal object
((ContentsList)getContents()).doAddUnique(((FInternalObjectImpl)object).getUserObject());
}
}
}
void touch(Fragment fragment) {
fragmentCache.touch(fragment);
}
void protect(Fragment fragment) {
fragmentCache.protect(fragment);
}
void unprotect(Fragment fragment) {
fragmentCache.unprotect(fragment);
}
public class Statistics {
private int creates = 0;
private int loads = 0;
private int unloads = 0;
public int getCreates() {
return creates;
}
public int getLoads() {
return loads;
}
public int getUnloads() {
return unloads;
}
public int getCacheSize() {
return fragmentCache.size();
}
}
private void unloadFragment(Fragment fragment) {
if (fragment.isLoaded()) {
statistics.unloads++;
EmfFragActivator.instance.debug("Saving and unloading fragment: " + fragment.getURI().toString());
try {
fragment.save(options);
} catch (IOException e) {
Throwables.propagate(e);
}
fragment.unload();
}
resourceSet.getResources().remove(fragment);
}
/**
* The {@link ResourceSet}s used to store a {@link FragmentedModel} need to
* match specific characteristics. These are: It has to create
* {@link Fragment}s and {@link FInternalObjectImpl}s on loading resources;
* it has to use a {@link DataStoreURIHandler}; it needs to refer to the
* reflective version of meta models; it needs specific performance
* configuration loading and saving models. This methods creates a
* {@link ResourceSet} with the listed characteristics.
*/
private ResourceSet createAndConfigureAResourceSet(IDataStore dataStore) {
ResourceSet resourceSet = new ResourceSetImpl() {
@Override
public EObject getEObject(URI uri, boolean loadOnDemand) {
if (uri.fragment() == null) {
// The URI must be a ID URI
EObject result = EmfFragActivator.instance.idSemantics.resolveURI(uri, FragmentedModel.this);
return result;
} else {
return super.getEObject(uri, loadOnDemand);
}
}
@Override
protected void demandLoad(Resource resource) throws IOException {
EmfFragActivator.instance.debug("Demand loaded fragment: " + resource.getURI().toString());
super.demandLoad(resource);
statistics.loads++;
}
};
resourceSet.getURIConverter().getURIHandlers().add(0, new DataStoreURIHandler(dataStore));
resourceSet.getLoadOptions().putAll(options);
resourceSet.getResourceFactoryRegistry().getProtocolToFactoryMap()
.put(dataStore.getURI().scheme(), new XMIResourceFactoryImpl() {
@Override
public Resource createResource(URI uri) {
EmfFragActivator.instance.debug("Created fragment: " + uri.toString());
Fragment fragment = newFragment(uri, FragmentedModel.this);
((ResourceImpl)fragment).setIntrinsicIDToEObjectMap(new HashMap<String, EObject>());
fragmentCache.put(uri, fragment);
return fragment;
}
});
resourceSet.setPackageRegistry(ReflectiveMetaModelRegistry.instance);
((ResourceSetImpl)resourceSet).setURIResourceMap(new HashMap<URI, Resource>());
return resourceSet;
}
/**
* Factory method for Fragments. Creates {@link XMIFragmentImpl} by default.
* Can be changed by subclasses.
*/
protected Fragment newFragment(URI uri, FragmentedModel model) {
return new XMIFragmentImpl(uri, model);
}
private class ContentsList extends ContentsEList<EObject> {
private static final long serialVersionUID = 1L;
@Override
public void doAddUnique(EObject object) {
super.doAddUnique(object);
}
@Override
public NotificationChain inverseAdd(EObject object, NotificationChain notifications) {
if (object instanceof FObjectImpl) {
FInternalObjectImpl internalObject = ((FObjectImpl)object).fInternalObject();
rootFragment.getContents().add(internalObject);
} else {
throw new IllegalArgumentException("Only FObjects can be added to fragmented models, generate you model code accordingly.");
}
FragmentedModel.this.attached(object);
return notifications;
}
@Override
public NotificationChain inverseRemove(EObject object, NotificationChain notifications) {
if (FragmentedModel.this.isLoaded) {
FragmentedModel.this.detached(object);
}
if (object instanceof FObjectImpl) {
FInternalObjectImpl internalObject = ((FObjectImpl)object).fInternalObject();
return internalObject.eSetResource(null, notifications);
} else {
throw new RuntimeException("Supposed unreachable.");
}
}
}
/**
* We use a version of {@link ContentsEList} that handles inverses differently, because
* the resource does not need to be set on the user objects. They know that
* they belong to a {@link FragmentedModel} differently.
*/
@Override
public EList<EObject> getContents() {
if (contents == null) {
contents = new ContentsList();
}
return contents;
}
public Root root() {
if (getContents().isEmpty()) {
Root root = EmfFragFactory.eINSTANCE.createRoot();
getContents().add(root);
}
EObject eObject = getContents().get(0);
if (eObject instanceof Root) {
return (Root)eObject;
} else {
return null;
}
}
ResourceSet getInternalResourceSet() {
return resourceSet;
}
Fragment createFragment(URI fragmentURI, FInternalObjectImpl root) {
if (fragmentURI == null) {
Long key = fragmentIndex.add();
fragmentURI = fragmentIndex.getURI(key);
}
Fragment newFragment = (Fragment) resourceSet.createResource(fragmentURI);
if (root != null) {
newFragment.getContents().add(root);
}
statistics.creates++;
return newFragment;
}
void deleteFragment(Fragment fragment) {
try {
fragment.delete(null);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Fragment
*/
@Override
public void load(Map<?, ?> options) throws IOException {
}
@Override
public void delete(Map<?, ?> options) throws IOException {
dataStore.drop();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void save(Map<?, ?> options) {
if (options != null) {
options.putAll((Map)FragmentedModel.options);
} else {
options = FragmentedModel.options;
}
for (Resource resource : resourceSet.getResources()) {
try {
resource.save(options);
} catch (IOException e) {
Throwables.propagate(e);
}
}
dataStore.flush();
}
/**
* Resolved the given URI that denotes a DB entry that contains a serialized
* fragment.
*
* @param uri
* The containment URI to resolve.
* @return The resolved object.
*/
public EObject resolveObjectURI(URI uri) {
return resourceSet.getEObject(uri, true);
}
public Statistics getStatistics() {
return statistics;
}
public IDataStore getDataStore() {
return dataStore;
}
IdIndex getIdIndex() {
return idIndex;
}
/**
* Only for testing purposes, hence package visibility.
*/
Fragment getFragment(URI fragmentURI) {
return (Fragment) getInternalResourceSet().getResource(fragmentURI, false);
}
private void assertStatistic(String name, int value, int min, int max) {
if (max != -1) {
Assert.assertTrue("Too many " + name + " " + value, value <= max);
}
if (min != -1) {
Assert.assertTrue("Too few " + name + " " + value, value >= min);
}
}
void assertNumberOfLoadedFragments(int min, int max) {
assertStatistic("loaded fragments", getInternalResourceSet().getResources().size(), min, max);
}
void assertNumberOfLoadedFragments(int size) {
assertNumberOfLoadedFragments(size, size);
}
void assertStatistics(int minLoaded, int maxLoaded, int minLoads, int maxLoads, int minUnloads, int maxUnloads,
int minCreates, int maxCreates) {
assertStatistic("loaded fragments", getInternalResourceSet().getResources().size(), minLoaded, maxLoaded);
assertStatistic("loads", statistics.getLoads(), minLoads, maxLoads);
assertStatistic("unloads", statistics.getUnloads(), minUnloads, maxUnloads);
assertStatistic("creates", statistics.getCreates(), minCreates, maxCreates);
}
private void assertIndex(IDataMap<Long> index, String name, long first, long last) {
if (first == -1) {
Assert.assertEquals("Wrong first key for " + name + ".", null, index.first());
} else {
Assert.assertEquals("Wrong first key for " + name + ".", (Long) first, index.first());
}
if (last == -1) {
Assert.assertEquals("Wrong last key for " + name + ".", null, index.last());
} else {
Assert.assertEquals("Wrong last key for " + name + ".", (Long) last, index.last());
}
}
void assertMaxFragmentsIndexSize(long max) {
Assert.assertTrue("Fragments index too large.", fragmentIndex.last() <= max);
}
void assertFragmentsIndex(long first, long last) {
assertIndex(fragmentIndex, "fragments index", first, last);
}
void assertIdIndex(long first, long last) {
assertIndex(idIndex, "id index", first, last);
}
void assertValueSetIndex(EObject owner, EStructuralFeature feature, long min, long max) {
dataStore.getMap(FValueSetList.createPrefix(((FObjectImpl) owner).fInternalObject(), feature).getBytes(),
LongKeyType.instance);
}
<KT> void assertIndexClassIndex(EObject owner, KT min, KT max, KeyType<KT> keyType) {
}
}