package org.simantics.interop.mapping; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.VirtualGraph; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.util.SessionGarbageCollection; import org.simantics.db.request.Read; import org.simantics.interop.mapping.data.GraphNode; import org.simantics.interop.mapping.data.Identifiable; import org.simantics.interop.mapping.data.Link; import org.simantics.interop.mapping.data.ResourceIdentifiable; import org.simantics.ui.jobs.SessionGarbageCollectorJob; import org.simantics.utils.datastructures.MapList; import org.simantics.utils.datastructures.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Marko Luukkainen * */ public class Mapper { private static final Logger LOGGER = LoggerFactory.getLogger(Mapper.class); public static final boolean USE_SPLIT_TRANSACTIONS = false; // Split transactions public static int OBJECTS_PER_TRANSACTION = 5000; // number of objects handled per transaction (split mode) private static boolean SLEEP_BETWEEN_WRITES = false; // sleep between transactions (split mode) private static int SLEEP_TIME = 10; // time to sleep (ms) private static boolean COLLECT_BETWEEN_WRITES = false; // Run SessionGC between split transactions private static boolean COLLECT_WITHIN_TRANSACTIONS = true; // Run Collect within transactions (both modes) public static int OBJECTS_BEFORE_COLLECT = 5000; // number of objects that are handled before collect (non-split mode) private List initializedRules = new ArrayList(); private List>> generationRules; private List> globalModificationRules; private List>> modificationRules; private List> connectionRules; int maxGenPass = 0; public Mapper() { long maxMemory = Runtime.getRuntime().maxMemory(); maxMemory /= (1024*1024); // Convert to MB; int use = 290; // Estimated memory usage of the system int freeMem = (int)maxMemory-use; // Free memory for mappings OBJECTS_BEFORE_COLLECT = (freeMem * freeMem) / 1000; //600M heap -> 84, 3000M heap -> 5645 if (OBJECTS_BEFORE_COLLECT < 2) OBJECTS_BEFORE_COLLECT = 2; OBJECTS_PER_TRANSACTION = OBJECTS_BEFORE_COLLECT; generationRules = new ArrayList>>(); modificationRules = new ArrayList>>(); globalModificationRules = new ArrayList>(); connectionRules = new ArrayList>(); } public void addRule(int pass, IdentificationRule idRule, GenerationRule genRule) { if (idRule == null || genRule == null) throw new NullPointerException(); generationRules.add(new Pair>(idRule, new Pair(pass,genRule))); maxGenPass = Math.max(maxGenPass, pass); if (genRule instanceof InitializedRule) initializedRules.add((InitializedRule)genRule); } public void addRule(IdentificationRule idRule, MappingRule mappingRule) { addRule(0,idRule,mappingRule); } public void addRule(IdentificationRule idRule, MappingRule... mappingRules) { for (MappingRule mappingRule : mappingRules) addRule(0,idRule,mappingRule); } public void addRule(int pass, IdentificationRule idRule, MappingRule mappingRule) { if (idRule == null || mappingRule == null) throw new NullPointerException(); if (mappingRule instanceof ModificationRule) { while (pass >= modificationRules.size()) { modificationRules.add(new ArrayList>()); } List> priList = modificationRules.get(pass); priList.add(new Pair(idRule, (ModificationRule)mappingRule)); } if (mappingRule instanceof GenerationRule) generationRules.add(new Pair>(idRule, new Pair(pass,(GenerationRule)mappingRule))); if (mappingRule instanceof InitializedRule) initializedRules.add((InitializedRule)mappingRule); } public void addRule(int pass, IdentificationRule idRule, MappingRule... mappingRules) { for (MappingRule mappingRule : mappingRules) addRule(pass,idRule,mappingRule); } public void addRule(IdentificationRule idRule, ModificationRule... modRules) { addRule(0, idRule, modRules); } public void addRule(int pass, IdentificationRule idRule, ModificationRule... modRules) { if (idRule == null) throw new NullPointerException(); while (pass >= modificationRules.size()) { modificationRules.add(new ArrayList>()); } List> priList = modificationRules.get(pass); for (ModificationRule modRule : modRules){ if (modRule == null) throw new NullPointerException(); priList.add(new Pair(idRule, modRule)); if (modRule instanceof InitializedRule) initializedRules.add((InitializedRule)modRule); } } public void addRule(ModificationRule modRule) { addRule(0, modRule); } public void addRule(InitializedRule initRule) { initializedRules.add(initRule); } public void addRule(int pass, ModificationRule modRule) { if (modRule == null) throw new NullPointerException(); while (pass >= globalModificationRules.size()) { globalModificationRules.add(new ArrayList()); } List priList = globalModificationRules.get(pass); priList.add(modRule); if (modRule instanceof InitializedRule) initializedRules.add((InitializedRule)modRule); } public void addRule(ConnectionIdentificationRule idRule, ConnectionGenerationRule genRule) { if (idRule == null || genRule == null) throw new NullPointerException(); connectionRules.add(new Pair(idRule, genRule)); if (genRule instanceof InitializedRule) initializedRules.add((InitializedRule)genRule); } /** * Runs the mapping procedure. Disposes nodes after mapping is done. * @param g * @param model * @param nodes * @param monitor * @throws Exception */ public void map(WriteGraph g, Resource model, Collection> nodes, IProgressMonitor monitor) throws Exception { startMapping(null); try { if (monitor == null) monitor = new NullProgressMonitor(); for (InitializedRule rule : initializedRules) rule.initialize(g, model); applyModifications(g, nodes, monitor); monitor.worked(1); applyGenerations(g,nodes,monitor); monitor.worked(1); applyConnections(g,nodes,monitor); monitor.worked(1); } finally { MappingTools.disposeNodes(nodes); endMapping(); } } /** * Runs the mapping procedure. Disposes nodes after mapping is done. * @param session * @param model * @param nodes * @param monitor * @throws Exception */ public void map(Session session, Resource model, Collection> nodes, IProgressMonitor monitor) throws Exception { map(session, model, null, nodes, monitor); } /** * Runs the mapping procedure. Disposes nodes after mapping is done. * @param session * @param model * @param vg * @param nodes * @param monitor * @throws Exception */ public void map(Session session, Resource model, VirtualGraph vg, Collection> nodes, IProgressMonitor monitor) throws Exception { startMapping(vg); try { long time = System.currentTimeMillis(); if (monitor == null) monitor = new NullProgressMonitor(); initializeRules(session, vg, model); if (USE_SPLIT_TRANSACTIONS) { applyModifications(session, nodes, monitor); monitor.worked(1); applyGenerations(session, vg, nodes, monitor); monitor.worked(1); applyConnections(session, vg, nodes, monitor); monitor.worked(1); } else { applyModifications2(session, nodes, monitor); monitor.worked(1); applyGenerations2(session, vg, nodes, monitor); monitor.worked(1); applyConnections2(session, vg, nodes, monitor); monitor.worked(1); } long time2 = System.currentTimeMillis(); System.out.println("Mapping took " + ((time2-time)/1000) + " seconds"); } finally { MappingTools.disposeNodes(nodes); if (COLLECT_BETWEEN_WRITES) { SessionGarbageCollection.gc(null, session, true, null); } endMapping(); } } //private boolean autosaveEndabled = false; private VirtualGraph vg; protected void startMapping(VirtualGraph vg) { SessionGarbageCollectorJob.getInstance().setEnabled(false); //autosaveEndabled = AutosaveCommands.getInstance().isEnabled(); //AutosaveCommands.getInstance().setEnabled(false); this.vg = vg; } protected void endMapping() { SessionGarbageCollectorJob.getInstance().setEnabled(true).scheduleAfterQuietTime(); // AutosaveCommands.getInstance().setEnabled(autosaveEndabled); vg = null; } private void applyModifications(ReadGraph g, Collection> nodes, IProgressMonitor monitor) throws Exception { // Apply global modification rules first int passCount = Math.max(globalModificationRules.size(),modificationRules.size()); for (int pass = 0; pass < passCount; pass++) { if (globalModificationRules.size() > pass) { int count = 0; List modRules = globalModificationRules.get(pass); int size = modRules.size(); monitor.subTask("Running global modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); for (ModificationRule r : modRules) { Collection> ruleModified = r.modify(g, nodes); if (ruleModified == null) continue; for (GraphNode m : ruleModified) { if (m.isDisposed()) { nodes.remove(m); } else if (!nodes.contains(m)) { nodes.add(m); } } monitor.subTask("Running global modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } if (COLLECT_WITHIN_TRANSACTIONS) collect(g); } } if (modificationRules.size() > pass) { int count = 0; List> modRules = modificationRules.get(pass); int size = modRules.size(); monitor.subTask("Running object modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); // Apply per object modification rules for (Pair modRule : modRules) { Collection> ruleModified = new ArrayList>(); for (GraphNode n : nodes) { applyModifications(g, n, modRule, ruleModified); if (COLLECT_WITHIN_TRANSACTIONS) collect2(g); } for (GraphNode m : ruleModified) { if (m.isDisposed()) { nodes.remove(m); } else if (!nodes.contains(m)) { nodes.add(m); } } monitor.subTask("Running object modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } } } } } protected void applyGenerations(WriteGraph graph, Collection> nodes, IProgressMonitor monitor) throws Exception { int size = nodes.size(); int count = 0; monitor.subTask("Assigning generation rules ("+ count + "/" + size + ")"); // populate generation rules for (GraphNode n : nodes) { for (Pair> r : generationRules) { if (r.first.matches(graph, n)) { MappingTools.assignGenerationRule(n, r.second.first,r.second.second); } } monitor.subTask("Assigning generation rules ("+ (++count) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } if (COLLECT_WITHIN_TRANSACTIONS) collect2(graph); } count = 0; monitor.subTask("Generating objects ("+ count + "/" + size + ")"); // apply generation rules. //WriteWrapper g = new WriteWrapper(graph); //Collection usedRules = new ArrayList(); for (int stage = 0; stage <= maxGenPass; stage++) { count = 0; for (GraphNode n : nodes) { MapList priRules = n.getHint(MappingHints.KEY_GENERATION_RULES); if (priRules != null) { List rules = priRules.getValues(stage); for (GenerationRule r : rules) { r.generate(graph, n); } } monitor.subTask("Generating objects, stage " + stage + " : ("+ (++count) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } if (COLLECT_WITHIN_TRANSACTIONS) collect2(graph); } } } protected void applyConnections(WriteGraph g, Collection> nodes, IProgressMonitor monitor) throws Exception { int size = nodes.size(); int count = 0; monitor.subTask("Generating connections ("+ count + "/" + size + ")"); for (GraphNode node : nodes) { applyConnections(g, node); monitor.subTask("Generating connections ("+ (++count) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } if (COLLECT_WITHIN_TRANSACTIONS) collect2(g); } } protected String getName(ReadGraph g, Identifiable res) throws DatabaseException { if (res instanceof ResourceIdentifiable) return NameUtils.getSafeName(g, ((ResourceIdentifiable)res).getResource()); else return res.toString(); } protected void initializeRules(Session session, VirtualGraph vg, final Resource model ) throws DatabaseException{ session.syncRequest(new WriteRequest(vg) { @Override public void perform(WriteGraph g) throws DatabaseException { for (InitializedRule rule : initializedRules) rule.initialize(g, model); } }); } protected void collect(ReadGraph g) throws DatabaseException { if (vg != null) return; SessionGarbageCollection.gc(g, 0, -1); } int collect = 0; protected void collect2(ReadGraph g) throws DatabaseException { if (vg != null) return; if (collect == OBJECTS_BEFORE_COLLECT) { SessionGarbageCollection.gc(g, 0, -1); collect = 0; } else { collect++; } } protected void applyModifications(Session session, final Collection> nodes, final IProgressMonitor monitor) throws Exception { int passCount = Math.max(globalModificationRules.size(),modificationRules.size()); for (int pass = 0; pass pass) { int count = 0; List modRules = globalModificationRules.get(pass); int size = modRules.size(); monitor.subTask("Running global modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); for (final ModificationRule r : modRules) { session.syncRequest(new ReadRequest() { @Override public void run(ReadGraph g) throws DatabaseException { try { Collection> ruleModified = r.modify(g, nodes); if (ruleModified == null) return; for (GraphNode m : ruleModified) { if (m.isDisposed()) { nodes.remove(m); } else if (!nodes.contains(m)) { nodes.add(m); } } if (COLLECT_WITHIN_TRANSACTIONS) collect(g); } catch (Exception e) { throw new DatabaseException(e); } } }); //SessionGarbageCollection.gc(null, session, true, null); monitor.subTask("Running global modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } } } if (modificationRules.size() > pass) { int count = 0; List> modRules = modificationRules.get(pass); int size = modRules.size(); monitor.subTask("Running object modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); // Apply per object modification rules for (final Pair modRule : modRules) { final Iterator> iter = nodes.iterator(); final Collection> ruleModified = new ArrayList>(); while (iter.hasNext()) { session.syncRequest(new ReadRequest() { @Override public void run(ReadGraph g) throws DatabaseException { try { int j = 0; //for (GraphNode n : nodes) { while (iter.hasNext() && j < OBJECTS_PER_TRANSACTION) { GraphNode n = iter.next(); applyModifications(g, n, modRule, ruleModified); j++; } if (COLLECT_WITHIN_TRANSACTIONS) collect(g); } catch (Exception e) { throw new DatabaseException(e); } } }); } for (GraphNode m : ruleModified) { if (m.isDisposed()) { nodes.remove(m); } else if (!nodes.contains(m)) { nodes.add(m); } } ruleModified.clear(); //SessionGarbageCollection.gc(null, session, true, null); monitor.subTask("Running object modification rules: pass (" + (pass+1) + "/" + passCount + "), rule ("+ (++count) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } } } } } private void applyModifications2(Session session, final Collection> nodes, final IProgressMonitor monitor) throws Exception { session.syncRequest(new ReadRequest() { @Override public void run(ReadGraph g) throws DatabaseException { try { applyModifications(g, nodes, monitor); } catch (Exception e) { throw new DatabaseException(e); } } }); } protected void applyGenerations(Session session, VirtualGraph vg, Collection> nodes, IProgressMonitor monitor) throws Exception { int size = nodes.size(); int count = 0; monitor.subTask("Assigning generation rules ("+ count + "/" + size + ")"); // populate generation rules { final Iterator> iter = nodes.iterator(); while (iter.hasNext()) { int c = session.syncRequest(new Read() { @Override public Integer perform(ReadGraph graph) throws DatabaseException { int j = 0; while (iter.hasNext() && j < OBJECTS_PER_TRANSACTION) { GraphNode n = iter.next(); for (Pair> r : generationRules) { if (r.first.matches(graph, n)) { MapList rules = n.getHint(MappingHints.KEY_GENERATION_RULES); if (rules == null) { rules = new MapList(); } rules.add(r.second.first,r.second.second); n.setHint(MappingHints.KEY_GENERATION_RULES, rules); } } j++; } if (COLLECT_WITHIN_TRANSACTIONS) collect(graph); return j; } }); collect(session); monitor.subTask("Assigning generation rules ("+ (count+=c) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } sleep(); } } count = 0; monitor.subTask("Generating objects ("+ (count) + "/" + size + ")"); // apply generation rules. { for (int stage = 0; stage <= maxGenPass; stage++) { final int fStage = stage; count = 0; final Iterator> iter = nodes.iterator(); while (iter.hasNext()) { int c = session.syncRequest(new WriteResultRequest(vg) { @Override public Integer perform(WriteGraph graph) throws DatabaseException { int j = 0; try { while (iter.hasNext() && j < OBJECTS_PER_TRANSACTION) { GraphNode n = iter.next(); MapList priRules = n.getHint(MappingHints.KEY_GENERATION_RULES); if (priRules == null) { j++; continue; } final List rules = priRules.getValues(fStage); if (fStage == 0 && rules.size() == 0) System.out.println(); for (GenerationRule r : rules) { r.generate(graph, n); } j++; } if (COLLECT_WITHIN_TRANSACTIONS) collect(graph); return j; } catch (Exception e) { throw new DatabaseException(e); } }}); collect(session); monitor.subTask("Generating objects, stage " + stage + " : ("+ (count+=c) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } sleep(); } } } } private void applyGenerations2(Session session, VirtualGraph vg, final Collection> nodes, final IProgressMonitor monitor) throws Exception { session.syncRequest(new WriteRequest(vg) { @Override public void perform(WriteGraph graph) throws DatabaseException { try { applyGenerations(graph, nodes, monitor); } catch (Exception e) { throw new DatabaseException(e); } } }); } private void collect(Session session) { if (COLLECT_BETWEEN_WRITES) SessionGarbageCollection.gc(null, session, true, null); } private void sleep() { if (SLEEP_BETWEEN_WRITES) { try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException e) { } } } protected void applyConnections(Session session, VirtualGraph vg, Collection> nodes, IProgressMonitor monitor) throws Exception { int size = nodes.size(); int count = 0; monitor.subTask("Generating connections ("+ count + "/" + size + ")"); final Iterator> iter = nodes.iterator(); while (iter.hasNext()) { int c = session.syncRequest(new WriteResultRequest(vg) { @Override public Integer perform(WriteGraph g) throws DatabaseException { int j = 0; try { while (iter.hasNext() && j < OBJECTS_PER_TRANSACTION) { GraphNode node = iter.next(); applyConnections(g, node); j++; } if (COLLECT_WITHIN_TRANSACTIONS) collect(g); return j; } catch (Exception e) { throw new DatabaseException(e); } } }); collect(session); monitor.subTask("Generating connections ("+ (count+=c) + "/" + size + ")"); if(monitor.isCanceled()) { throw new CancelException("Cancel requested."); } sleep(); } } private void applyConnections2(Session session, VirtualGraph vg, final Collection> nodes, final IProgressMonitor monitor) throws Exception { session.syncRequest(new WriteRequest(vg) { @Override public void perform(WriteGraph graph) throws DatabaseException { try { applyConnections(graph, nodes, monitor); } catch (Exception e) { throw new DatabaseException(e); } } }); } protected void applyModifications(ReadGraph g, GraphNode n, Pair modRule, Collection> ruleModified) throws Exception { if (!n.isDisposed() && modRule.first.matches(g, n)) { // we have to check Collection> perRule = new ArrayList>(); perRule.add(n); ruleModified.addAll(modRule.second.modify(g, perRule)); } } protected void applyConnections(WriteGraph g, GraphNode node) throws Exception { for (Link link : node.getLinks()) { if (link.isMain()) { for (Pair r : connectionRules) { if (r.first.mathces(g, node, link.to(), link)) { LOGGER.info("Connecting " + getName(g, node.getData()) + " to " + getName(g, link.to().getData()) + " " + link.getName() + "/"+link.getInverseName()); r.second.generate(g, node, link.to(), link); break; } } } } } }