package org.simantics.modeling; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.FileLocator; import org.osgi.framework.Bundle; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.IndexRoot; import org.simantics.db.common.request.ObjectsWithType; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.utils.Logger; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.adapter.CopyHandler; import org.simantics.db.layer0.adapter.SubgraphExtent.ExtentStatus; import org.simantics.db.layer0.adapter.impl.DefaultCopyHandler; import org.simantics.db.layer0.adapter.impl.DefaultPasteHandler; import org.simantics.db.layer0.adapter.impl.DefaultPasteImportAdvisor; import org.simantics.db.layer0.util.ClipboardUtils; import org.simantics.db.layer0.util.DomainProcessorState; import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.db.layer0.util.ModelTransferableGraphSource; import org.simantics.db.layer0.util.ModelTransferableGraphSourceRequest; import org.simantics.db.layer0.util.SimanticsClipboard.Representation; import org.simantics.db.layer0.util.SimanticsClipboardImpl; import org.simantics.db.layer0.util.SimanticsKeys; import org.simantics.db.layer0.util.TransferableGraphConfiguration2; import org.simantics.db.service.SerialisationSupport; import org.simantics.graph.compiler.CompilationResult; import org.simantics.graph.compiler.ExternalFileLoader; import org.simantics.graph.compiler.GraphCompiler; import org.simantics.graph.compiler.GraphCompilerPreferences; import org.simantics.graph.compiler.ValidationMode; import org.simantics.graph.db.TransferableGraphException; import org.simantics.graph.db.TransferableGraphSource; import org.simantics.graph.db.TransferableGraphs; import org.simantics.graph.diff.Diff; import org.simantics.graph.diff.TransferableGraphDelta1; import org.simantics.graph.representation.Identity; import org.simantics.graph.representation.Root; import org.simantics.graph.representation.TransferableGraph1; import org.simantics.graphfile.ontology.GraphFileResource; import org.simantics.layer0.Layer0; import org.simantics.ltk.ISource; import org.simantics.ltk.Problem; import org.simantics.modeling.internal.Activator; import org.simantics.utils.FileUtils; import org.simantics.utils.datastructures.Pair; /** * @author Antti Villberg */ public class CompilePGraphs { public static interface UserAgent { void reportProblems(CompilationResult result); } /** * Simantics/PGraph#compilePGraphs SCL API. * * @param r * @throws IOException * @throws DatabaseException */ public static void compilePGraphs(Resource r) throws IOException, DatabaseException { compilePGraphs(r, null); } public static void compilePGraphs(Resource r, UserAgent userAgent) throws IOException, DatabaseException { final Collection sources = new ArrayList<>(); Collection dependencies = new ArrayList<>(); final Pair thisOntology = Simantics.sync(new UniqueRead>() { @Override public Pair perform(ReadGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); Resource parent = graph.getSingleObject(r, L0.PartOf); CopyHandler ch = new DefaultCopyHandler(r) { protected TransferableGraphConfiguration2 createConfiguration(ReadGraph graph, boolean cut) throws DatabaseException { Map preStatus = new HashMap<>(); preStatus.put(r, ExtentStatus.EXTERNAL); if (!parent.equals(graph.getRootLibrary())) preStatus.put(parent, ExtentStatus.EXTERNAL); return new TransferableGraphConfiguration2(null, Collections.emptyList(), preStatus, true, true); } protected TransferableGraphSource computeSource(ReadGraph graph, TransferableGraphConfiguration2 conf) throws DatabaseException { return graph.syncRequest(new ModelTransferableGraphSourceRequest(conf) { protected ModelTransferableGraphSource getSource(ReadGraph graph, TransferableGraphConfiguration2 configuration, DomainProcessorState state, File otherStatementsFile, File valueFile) throws DatabaseException { return new ModelTransferableGraphSource(graph, configuration, state, otherStatementsFile, valueFile) { @Override protected Identity getRootIdentity(DomainProcessorState state, SerialisationSupport support, Resource rootLibrary) throws DatabaseException { return new Identity(state.ids.get(support.getTransientId(rootLibrary)), new Root("", "")); } }; } }); } }; String uri = graph.getURI(r); SimanticsClipboardImpl clipboard = new SimanticsClipboardImpl(); ch.copyToClipboard(graph, clipboard); for (Set object : clipboard.getContents()) { TransferableGraph1 tg = ClipboardUtils.accept(graph, object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH); if (tg != null) return Pair.make(uri, tg); } return null; } }); if (thisOntology == null) throw new DatabaseException("Failed to dump the containing ontology of " + r + " into TransferableGraph1"); dependencies.add(thisOntology.second); for (Bundle b : Activator.getContext().getBundles()) { String id = b.getSymbolicName(); String name = (String) b.getHeaders().get("Bundle-Name"); if (name == null) name = id; if (name.equals(thisOntology.first)) continue; URL tg = b.getEntry("/graph.tg"); if (tg == null) continue; File f = url2file(FileLocator.resolve(tg), b.getSymbolicName()); try { dependencies.add(GraphCompiler.read(f)); } catch (Exception e) { throw new IOException("Failed to read compiled transferable graph as dependency: " + f, e); } } Simantics.sync(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); for (Resource file : graph.syncRequest(new ObjectsWithType(r, L0.ConsistsOf, L0.PGraph))) { String src = graph.getRelatedValue(file, L0.PGraph_definition, Bindings.STRING); sources.add(new StringSource(src)); } } }); GraphCompilerPreferences prefs = new GraphCompilerPreferences(); prefs.validate = true; prefs.validateRelationRestrictions = ValidationMode.ERROR; prefs.validateResourceHasType = ValidationMode.IGNORE; final CompilationResult result = Simantics.sync(new UniqueRead() { @Override public CompilationResult perform(ReadGraph graph) throws DatabaseException { final Resource root = graph.syncRequest(new IndexRoot(r)); final String baseURI = graph.getURI(root); GraphFileResource GF = GraphFileResource.getInstance(graph); ExternalFileLoader fileLoader = fileName -> { try { Resource file = graph.getResource(baseURI + "/" + fileName); return graph.getRelatedValue(file, GF.HasFiledata, Bindings.BYTE_ARRAY); } catch (DatabaseException e) { throw new IOException(e); } }; return GraphCompiler.compile("1.1", sources, dependencies, fileLoader, prefs); } }); if (!result.getErrors().isEmpty() || !result.getWarnings().isEmpty()) { if (userAgent != null) { userAgent.reportProblems(result); return; } else { StringBuilder error = new StringBuilder(); for (Problem problem : result.getErrors()) error.append(problem.getLocation() + ": " + problem.getDescription() + "\n"); for (Problem problem : result.getWarnings()) error.append(problem.getLocation() + ": " + problem.getDescription() + "\n"); throw new DatabaseException(error.toString()); } } final Pair existing = Simantics.sync(new UniqueRead>() { @Override public Pair perform(ReadGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); TransferableGraph1 tg = graph.getPossibleRelatedValue(r, L0.SharedOntology_tg, Bindings.getBindingUnchecked( TransferableGraph1.class )); if(tg == null) return null; long[] tgResources = graph.getPossibleRelatedValue(r, L0.SharedOntology_tgResources, Bindings.LONG_ARRAY); if(tgResources == null) return null; return Pair.make(tg, tgResources); } }); if (existing != null) { Simantics.sync(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { TransferableGraphDelta1 delta = new Diff(existing.first, result.getGraph()).diff(); long[] resourceArray = TransferableGraphs.applyDelta(graph, existing.second, delta); Layer0 L0 = Layer0.getInstance(graph); graph.claimLiteral(r, L0.SharedOntology_tg, result.getGraph(), Bindings.getBindingUnchecked( TransferableGraph1.class )); graph.claimLiteral(r, L0.SharedOntology_tgResources, L0.ResourceIdArray, resourceArray, Bindings.LONG_ARRAY); Layer0Utils.addCommentMetadata(graph, "Compiled ontology " + graph.getURI(r)); } }); } else { final DefaultPasteImportAdvisor advisor = new DefaultPasteImportAdvisor(r); try { DefaultPasteHandler.defaultExecute(result.getGraph(), r, advisor); } catch (TransferableGraphException e) { // TODO: defaultExecute never actually throws this exception! throw new DatabaseException(e); } Simantics.sync(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); graph.claimLiteral(r, L0.SharedOntology_tg, result.getGraph(), Bindings.getBindingUnchecked( TransferableGraph1.class )); graph.claimLiteral(r, L0.SharedOntology_tgResources, L0.ResourceIdArray, advisor.getResourceIds(), Bindings.LONG_ARRAY); Layer0Utils.addCommentMetadata(graph, "Compiled ontology " + graph.getURI(r)); } }); } } private static File extractLib(URL libURL, String libName) throws FileNotFoundException, IOException { String tmpDirStr = System.getProperty("java.io.tmpdir"); if (tmpDirStr == null) throw new NullPointerException("java.io.tmpdir property is null"); File tmpDir = new File(tmpDirStr); File libFile = new File(tmpDir, libName); return FileUtils.copyResource(libURL, libFile, false); } private static File url2file(URL url, String fileName) { if ("file".equals(url.getProtocol())) { try { File path = new File(URLDecoder.decode(url.getPath(), "UTF-8")); return path; } catch (UnsupportedEncodingException e) { Logger.defaultLogError(e); } } else if ("jar".equals(url.getProtocol())) { try { File libFile = extractLib(url, fileName); return libFile; } catch (FileNotFoundException e) { Logger.defaultLogError(e); } catch (IOException e) { Logger.defaultLogError(e); } } else { System.err.println("Unsupported URL protocol '" + url + "' for FastLZ native library file '" + fileName); } return null; } private static class StringSource implements ISource { private String src; private ByteArrayInputStream baos; public StringSource(String s) { this.src = s; this.baos = new ByteArrayInputStream(src.getBytes()); } @Override public int startPos() { return 0; } @Override public int startLine() { return 0; } @Override public InputStream open() throws IOException { return baos; } @Override public int length() throws IOException { return src.length(); } @Override public String getName() { return "Source"; } } }