/** * <copyright> * Copyright (c) 2010-2014 Henshin developers. 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 * </copyright> */ package org.eclipse.emf.henshin.model.resource; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.ecore.plugin.EcorePlugin; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xmi.XMIResource; import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl; import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.eclipse.emf.henshin.model.Attribute; import org.eclipse.emf.henshin.model.Edge; import org.eclipse.emf.henshin.model.HenshinPackage; import org.eclipse.emf.henshin.model.Module; import org.eclipse.emf.henshin.model.Node; /** * Resource set implementation for Henshin. * Provides some convenience methods for easy use * and supports automatic resolving of relative * file URIs using a base directory. * * @author Christian Krause */ public class HenshinResourceSet extends ResourceSetImpl { /** * Base directory URI converter. If the base directory is set and * a relative file URI should be converted, then the relative file * URI is resolved using the base directory. */ private class BaseDirURIConverter extends ExtensibleURIConverterImpl { @Override public URI normalize(URI uri) { if (uri.isFile() && uri.isRelative() && baseDir!=null) { return uri.resolve(baseDir); } else { return super.normalize(uri); } } } /** * Absolute file path of the base directory as a file URI. */ private URI baseDir; /** * Constructor which sets the base directory for this resource set. * * @param baseDir Base directory (can be also <code>null</code>). */ public HenshinResourceSet(String baseDir) { // Make sure the standard packages are initialized: EcorePackage.eINSTANCE.getName(); HenshinPackage.eINSTANCE.getName(); initPackageImplementation("org.eclipse.emf.henshin.trace.TracePackage"); initPackageImplementation("org.eclipse.emf.henshin.wrap.WrapPackage"); // Register common XMI file resource factories: if (!EcorePlugin.IS_ECLIPSE_RUNNING) { registerXMIResourceFactories(HenshinResource.FILE_EXTENSION, "ecore", "xmi"); } // Set the base directory: if (baseDir!=null) { baseDir = new File(baseDir).getAbsolutePath(); if (!baseDir.endsWith(File.separator)) { baseDir = baseDir + File.separator; } this.baseDir = URI.createFileURI(baseDir); setURIConverter(new BaseDirURIConverter()); } } /** * Constructor without base directory. */ public HenshinResourceSet() { this(null); } /** * Get the base directory for this resource set as a file URI. * * @return The base directory as a file URI or <code>null</code>. */ public URI getBaseDir() { return baseDir; } /** * Register {@link XMIResourceFactoryImpl}s for the given file extensions. * The factories are registered in the scope of this resource set. The * resource factories are registered only if no other resource factory * is already registered for the file extension. * * @param fileExtension File extensions. */ public void registerXMIResourceFactories(String... fileExtensions) { Map<String, Object> map = getResourceFactoryRegistry().getExtensionToFactoryMap(); for (String extension : fileExtensions) { Resource.Factory factory; if (HenshinResource.FILE_EXTENSION.equals(extension)) { factory = new HenshinResourceFactory(); } else if ("ecore".equals(extension)) { factory = new EcoreResourceFactoryImpl(); } else { factory = new XMIResourceFactoryImpl(); } if (!map.containsKey(extension)) { map.put(extension, factory); } } } /** * Load {@link EPackage}s from an Ecore file and * register them in the local package registry. * * @param ecorePath The relative path to an Ecore file. * @return List of loaded and registered {@link EPackage}s. */ public List<EPackage> registerDynamicEPackages(String ecorePath) { List<EPackage> result = new ArrayList<EPackage>(); try { Resource resource = getResource(ecorePath); Iterator<EObject> it = resource.getAllContents(); while (it.hasNext()) { EObject next = it.next(); if (next instanceof EPackage) { result.add((EPackage) next); getPackageRegistry().put(((EPackage) next).getNsURI(), next); } } } catch (Throwable t) { t.printStackTrace(); } return result; } /** * Try to initialize a generated package implementation. Note that * this has a global effect (not limited to this resource set). * * @param packageClassName Class name of the (interface) of a package implementation. * @return <code>true</code> if the package was successfully initialized. */ public boolean initPackageImplementation(String packageClassName) { try { Class<?> clazz = Class.forName(packageClassName); return (clazz!=null && clazz.getField("eINSTANCE").get(clazz)!=null); } catch (Throwable t) { return false; } } /** * Load a resource for the given (relative) path and file name. * If the path is relative, it will be resolved using the base * directory of this resource set. * * @param path Possible relative model path. * @return The loaded resource. */ public Resource getResource(String path) { return getResource(URI.createFileURI(path), true); } /** * Create a resource for a given path. * @see #getResource(String) * @see #createResource(URI) * @param path Possible relative model path. * @return The created resource. */ public Resource createResource(String path) { return createResource(URI.createFileURI(path)); } /** * Load a resource for the given file name and get the first * {@link EObject} contained in it. If the path is relative, * it will be resolved using the base directory of this resource set. * * @param path Possible relative path and file name. * @return The first contained object. */ public EObject getEObject(String path) { Resource resource = getResource(path); if (resource!=null && !resource.getContents().isEmpty()) { return resource.getContents().get(0); } return null; } /** * Load a {@link Module} from a Henshin file given as a * path and file name. If the path is relative, it will be * resolved using the base directory of this resource set. * The behavior is as in {@link #getModule(URI, boolean)}. * * @see #getModule(URI, boolean) * @param path Possibly relative path to a Henshin file. * @param fixImports If <code>true</code>, tries to fix the imports of the loaded module (default is <code>false</code>). * @return The contained {@link Module}. */ public Module getModule(String path, boolean fixImports) { return getModule(URI.createFileURI(path), fixImports); } /** * <p> * Load a {@link Module} from a Henshin file given by a URI. * </p> * <p> * If <code>fixImports</code> is set to <code>true</code>, * the method will try to fix broken imports of the module. * Specifically, it will check for every imported package * of the module, if a another package with the same namespace * URI is registered in the local package registry of this * resource. If yes, all references to elements of this package * in the module will be replaced by the found package. * </p> * <p> * If you want to fix the imports, you should first load the * instance models to be transformed, and then call this method. * </p> * * @param path Possible relative path and file name of a Henshin resource. * @param fixImports If <code>true</code>, tries to fix the imports of the loaded module (default is <code>false</code>). * @return The contained {@link Module}. */ public Module getModule(URI uri, boolean fixImports) { // Try to load the module: Module module = null; Resource resource = getResource(uri, true); if (resource!=null) { for (EObject object : resource.getContents()) { if (object instanceof Module) { module = (Module) object; break; } } } // Fix imports? if (module!=null && fixImports) { Iterator<EObject> it = module.eAllContents(); while (it.hasNext()) { EObject obj = it.next(); if (obj instanceof Node) { // nodes Node n = (Node) obj; EPackage real = getRealEPackage(n.getType().getEPackage()); if (real!=null) { n.setType((EClass) real.getEClassifier(n.getType().getName())); } } else if (obj instanceof Edge) { // edges Edge e = (Edge) obj; EPackage real = getRealEPackage(e.getType().getEContainingClass().getEPackage()); if (real!=null) { EClass owner = (EClass) real.getEClassifier(e.getType().getEContainingClass().getName()); e.setType((EReference) owner.getEStructuralFeature(e.getType().getName())); } } else if (obj instanceof Attribute) { // attributes Attribute a = (Attribute) obj; EPackage real = getRealEPackage(a.getType().getEContainingClass().getEPackage()); if (real!=null) { EClass owner = (EClass) real.getEClassifier(a.getType().getEContainingClass().getName()); a.setType((EAttribute) owner.getEStructuralFeature(a.getType().getName())); } } } } return module; } /** * Load a {@link Module} from a Henshin file given as a * path and file name. If the path is relative, it will be * resolved using the base directory of this resource set. * This does not fix imports. * * @see #getModule(String, boolean) * @param path Possibly relative path to a Henshin file. * @return The contained {@link Module}. */ public Module getModule(String path) { return getModule(path, false); } /** * Get the real package that should be used. This checks whether there is * a different {@link EPackage} registered in this resource set's package registry * under the same namespace URI as the given used package. If yes, this package * will be returned, otherwise <code>null</code>. * * @param usedPackage The used package in a module. * @return The correct package or <code>null</code>. */ private EPackage getRealEPackage(EPackage usedPackage) { EPackage realPackage = getPackageRegistry().getEPackage(usedPackage.getNsURI()); if (realPackage!=null && realPackage!=usedPackage) { return realPackage; } return null; } /** * Save an {@link EObject} at a given path. This creates a new resource * under the given path, adds the object to the resource and saves it. * This delegates to {@link #saveEObject(EObject, URI)}. * * @param object {@link EObject} to be saved. * @param path Possibly relative file path. */ public void saveEObject(EObject object, String path) { saveEObject(object, URI.createFileURI(path)); } /** * Save an {@link EObject} at a given URI. This creates a new resource * under the given path, adds the object to the resource and saves it. * * @param object {@link EObject} to be saved. * @param uri URI pointing to the file where the object should be saved. */ public void saveEObject(EObject object, URI uri) { Resource resource = createResource(uri); resource.getContents().clear(); resource.getContents().add(object); Map<Object,Object> options = new HashMap<Object,Object>(); options.put(XMIResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE); try { resource.save(options); } catch (IOException e) { throw new RuntimeException(e); } } }