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.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
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.compiler.internal.ltk.ISource;
import org.simantics.graph.compiler.internal.ltk.Problem;
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.modeling.internal.Activator;
import org.simantics.utils.FileUtils;
import org.simantics.utils.datastructures.Pair;
import org.slf4j.LoggerFactory;
/**
* @author Antti Villberg
*/
public class CompilePGraphs {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(CompilePGraphs.class);
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 {
compilePGraphs(r, userAgent, new NullProgressMonitor());
}
public static void compilePGraphs(Resource r, UserAgent userAgent, IProgressMonitor monitor) 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, monitor);
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));
}
});
}
// Delete index to get rid of floating old instances of the same ontology
// DatabaseIndexing.deleteAllIndexes();
}
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 {
LOGGER.warn("Unsupported URL protocol");
}
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";
}
}
}