/******************************************************************************* * Copyright (c) 2008-2011 Chair for Applied Software Engineering, * Technische Universitaet Muenchen. * 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: ******************************************************************************/ package org.eclipse.emf.emfstore.common.model.util; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.IStatus; 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.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; /** * Implements a list for a containment reference that will automatically split into several resources and save them * whenever necessary ude to operations on the list. The containmentList is part of a root object that holds the * containment list. Elements added to the list will be saved to files in the given path with a randomly assigned names * and the given extension. The list does not support the set method. * * @author koegel * @param <T> */ public class AutoSplitAndSaveResourceContainmentList<T extends EObject> implements List<T> { private static final String ROOT_NAME = "root"; private static final int MAX_CAPACITY = 30; private final EList<T> list; private Resource currentResource; private int currentResourceElementCount; private final ResourceSet resourceSet; private final String path; private final String extension; private Resource rootResource; private HashSet<Resource> dirtyResourceSet; /** * Constructor. * * @param root parent element of list * @param list the containment list * @param resourceSet the resourceSet to create the resources in * @param path the path for new resources * @param extension the file extension for new resources */ public AutoSplitAndSaveResourceContainmentList(EObject root, EList<T> list, ResourceSet resourceSet, String path, String extension) { if (list == null || resourceSet == null || path == null || extension == null || root == null) { throw new IllegalArgumentException(); } this.resourceSet = resourceSet; this.dirtyResourceSet = new HashSet<Resource>(); this.path = path; this.extension = extension; this.list = list; Resource eResource = root.eResource(); if (eResource == null) { URI fileURI = URI.createFileURI(path + File.separatorChar + ROOT_NAME + extension); rootResource = resourceSet.createResource(fileURI); rootResource.getContents().add(root); markAsDirty(rootResource); saveDirtyResources(); } else { rootResource = eResource; } // init first resource initCurrentResource(resourceSet); } private void initCurrentResource(ResourceSet resourceSet) { currentResource = createRandomResource(resourceSet, this.path); currentResourceElementCount = 0; } private Resource createRandomResource(ResourceSet resourceSet, String path) { URI fileURI = URI.createFileURI(path + File.separatorChar + UUID.randomUUID().toString() + extension); return resourceSet.createResource(fileURI); } /** * {@inheritDoc} * * @see java.util.List#add(int, java.lang.Object) */ public void add(int index, T element) { addToResource(element); list.add(index, element); markAsDirty(rootResource); saveDirtyResources(); } /** * {@inheritDoc} * * @see java.util.List#add(java.lang.Object) */ public boolean add(T o) { addToResource(o); boolean result = list.add(o); markAsDirty(rootResource); saveDirtyResources(); return result; } private void addToResource(T o) { if (o.eResource() != null) { return; } URI uri = currentResource.getURI(); File file = new File(uri.toFileString()); // TODO: magic number if (currentResourceElementCount > MAX_CAPACITY || file.length() > 100000) { currentResource = createRandomResource(resourceSet, path); currentResourceElementCount = 0; } currentResource.getContents().add(o); markAsDirty(currentResource); currentResourceElementCount += 1; } private void removeFromResource(EObject o) { Resource eResource = o.eResource(); if (eResource == null) { return; } eResource.getContents().remove(o); markAsDirty(eResource); if (eResource == currentResource) { currentResourceElementCount -= 1; } } private void markAsDirty(Resource resource) { dirtyResourceSet.add(resource); } /** * {@inheritDoc} * * @see java.util.List#addAll(java.util.Collection) */ public boolean addAll(Collection<? extends T> c) { for (T element : c) { addToResource(element); } boolean result = list.addAll(c); markAsDirty(rootResource); saveDirtyResources(); return result; } /** * {@inheritDoc} * * @see java.util.List#addAll(int, java.util.Collection) */ public boolean addAll(int index, Collection<? extends T> c) { for (T element : c) { addToResource(element); } boolean result = list.addAll(index, c); markAsDirty(rootResource); saveDirtyResources(); return result; } /** * {@inheritDoc} * * @see java.util.List#clear() */ public void clear() { list.clear(); File dir = new File(path); File[] listFiles = dir.listFiles(); if (listFiles != null) { for (File file : listFiles) { if (file.isDirectory() || !file.getName().endsWith(extension)) { continue; } if (file.getName().endsWith(ROOT_NAME + extension)) { continue; } file.delete(); } } initCurrentResource(resourceSet); markAsDirty(rootResource); saveDirtyResources(); } /** * {@inheritDoc} * * @see java.util.List#contains(java.lang.Object) */ public boolean contains(Object o) { return list.contains(o); } /** * {@inheritDoc} * * @see java.util.List#containsAll(java.util.Collection) */ public boolean containsAll(Collection<?> c) { return list.containsAll(c); } /** * {@inheritDoc} * * @see java.util.List#get(int) */ public T get(int index) { return list.get(index); } /** * {@inheritDoc} * * @see java.util.List#indexOf(java.lang.Object) */ public int indexOf(Object o) { return list.indexOf(o); } /** * {@inheritDoc} * * @see java.util.List#isEmpty() */ public boolean isEmpty() { return list.isEmpty(); } /** * {@inheritDoc} * * @see java.util.List#iterator() */ public Iterator<T> iterator() { return list.iterator(); } /** * {@inheritDoc} * * @see java.util.List#lastIndexOf(java.lang.Object) */ public int lastIndexOf(Object o) { return list.lastIndexOf(o); } /** * {@inheritDoc} * * @see java.util.List#listIterator() */ public ListIterator<T> listIterator() { return list.listIterator(); } /** * {@inheritDoc} * * @see java.util.List#listIterator(int) */ public ListIterator<T> listIterator(int index) { return list.listIterator(index); } /** * {@inheritDoc} * * @see java.util.List#remove(int) */ public T remove(int index) { T t = list.remove(index); removeFromResource(t); markAsDirty(rootResource); saveDirtyResources(); return t; } /** * {@inheritDoc} * * @see java.util.List#remove(java.lang.Object) */ @SuppressWarnings("unchecked") public boolean remove(Object o) { boolean remove = list.remove(o); if (o instanceof EObject) { removeFromResource((T) o); } markAsDirty(rootResource); saveDirtyResources(); return remove; } /** * {@inheritDoc} * * @see java.util.List#removeAll(java.util.Collection) */ @SuppressWarnings("unchecked") public boolean removeAll(Collection<?> c) { boolean result = list.removeAll(c); for (Object o : c) { if (o instanceof EObject) { removeFromResource((T) o); } } markAsDirty(rootResource); saveDirtyResources(); return result; } /** * {@inheritDoc} * * @see java.util.List#retainAll(java.util.Collection) */ @SuppressWarnings("unchecked") public boolean retainAll(Collection<?> c) { Set<Object> removedElements = new HashSet<Object>(); removedElements.addAll(list); removedElements.removeAll(c); boolean result = list.retainAll(c); for (Object o : removedElements) { if (o instanceof EObject) { removeFromResource((T) o); } } markAsDirty(rootResource); saveDirtyResources(); return result; } private void saveDirtyResources() { int threads = Runtime.getRuntime().availableProcessors(); ExecutorService execService = Executors.newFixedThreadPool(threads); int resourcesPerThread = (dirtyResourceSet.size() + threads - 1) / threads; final ArrayList<Resource> resources = new ArrayList<Resource>(dirtyResourceSet); for (int i = 0; i < resources.size(); i += resourcesPerThread) { final int min = i; final int max = Math.min(min + resourcesPerThread, resources.size()); execService.submit(new Runnable() { public void run() { for (int j = min; j <= max; j++) { try { resources.get(j).save(null); } catch (IOException e) { String message = "Saving to resource failed!"; ModelUtil.log(message, e, IStatus.ERROR); throw new IllegalStateException(message, e); } } } }); } execService.shutdown(); try { // wait for 30 minutes to finish save boolean terminated = execService.awaitTermination(1800000, TimeUnit.MILLISECONDS); if (!terminated) { throw new IllegalStateException("FAIL: Save did not complete within time limit."); } } catch (InterruptedException e1) { String message = "Saving to resource failed!"; throw new IllegalStateException(message, e1); } dirtyResourceSet.clear(); } /** * Not implemented. Will throw exception on call. * * @param index the index * @param element the new element * @return will always throw an exception * @see java.util.List#set(int, java.lang.Object) */ public T set(int index, T element) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} * * @see java.util.List#size() */ public int size() { return list.size(); } /** * {@inheritDoc} * * @see java.util.List#subList(int, int) */ public List<T> subList(int fromIndex, int toIndex) { return list.subList(fromIndex, toIndex); } /** * {@inheritDoc} * * @see java.util.List#toArray() */ public Object[] toArray() { return list.toArray(); } /** * {@inheritDoc} * * @see java.util.List#toArray(T[]) */ public <D> D[] toArray(D[] a) { return list.toArray(a); } }