diff --git a/org.emoflon.gips.build/META-INF/MANIFEST.MF b/org.emoflon.gips.build/META-INF/MANIFEST.MF index f00d950c..83f8b1eb 100644 --- a/org.emoflon.gips.build/META-INF/MANIFEST.MF +++ b/org.emoflon.gips.build/META-INF/MANIFEST.MF @@ -16,6 +16,8 @@ Require-Bundle: org.emoflon.gips.intermediate;bundle-version="1.5.0", org.emoflon.ibex.gt, org.moflon.core.plugins, org.emoflon.gips.core.dependencies, + org.emoflon.gips.debugger.trace, + org.emoflon.gips.debugger, org.junit, junit-jupiter-api Bundle-Vendor: Real-Time Systems Lab - TU Darmstadt diff --git a/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsBuilderUtils.java b/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsBuilderUtils.java index 3eb24ab4..edb1afdf 100644 --- a/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsBuilderUtils.java +++ b/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsBuilderUtils.java @@ -16,6 +16,7 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; @@ -29,6 +30,13 @@ import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.emoflon.gips.build.generator.GipsImportManager; +import org.emoflon.gips.build.transformation.GipsToIntermediate; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.api.ITraceManager; +import org.emoflon.gips.debugger.trace.TraceMap; +import org.emoflon.gips.debugger.trace.TraceModelLink; +import org.emoflon.gips.debugger.trace.resolver.ResolveEcore2Id; +import org.emoflon.gips.debugger.utility.HelperEclipse; import org.emoflon.gips.intermediate.GipsIntermediate.GipsIntermediateModel; import org.emoflon.gips.intermediate.GipsIntermediate.TypeConstraint; import org.emoflon.ibex.gt.codegen.EClassifiersManager; @@ -151,7 +159,7 @@ public static URI saveResource(EObject object, String path) { saveOptions.put(XMLResource.OPTION_SAVE_TYPE_INFORMATION, Boolean.TRUE); saveOptions.put(XMLResource.OPTION_SCHEMA_LOCATION_IMPLEMENTATION, Boolean.TRUE); try { - ((XMIResource) output).save(saveOptions); + output.save(saveOptions); } catch (IOException e) { e.printStackTrace(); } @@ -292,6 +300,49 @@ public static void generateEMoflonAPI(final IProject project, final GipsAPIData }); } + /** + * Updates the project specific model transformation trace + * + * @param project used to determine the project to be update + * @param gipslModelURI used to generate an id for the gipsl model + * @param intermediateModelURI used to generate an id the intermediate model + * @param transformer Provides the gipsl-intermediate trace + */ + public static void updateTrace(final IProject project, final URI gipslModelURI, final URI intermediateModelURI, + final GipsToIntermediate transformer) { + + // each project has it's own trace. + ITraceContext traceContext = ITraceManager.getInstance().getContext(project.getName()); + + try { + // adjust file extension from xmi to gipsl + URI gipslURI = HelperEclipse.toPlatformURI(gipslModelURI.trimFileExtension().appendFileExtension("gipsl")); + // first segment of an URI is the project name, by removing it we get a + // project relative path + IPath gipslPath = IPath.fromOSString(gipslURI.toPlatformString(true)).makeRelative().removeFirstSegments(1); + + URI intermediateURI = HelperEclipse.toPlatformURI(intermediateModelURI); + // FIXME: the intermediate URI isn't a valid URI. The first segment is not the + // project name. + IPath intermediatePath = IPath.fromOSString(intermediateURI.toPlatformString(true)).makeRelative(); + + String gipslModelId = gipslPath.toString(); // gipslModelURI.trimFileExtension().lastSegment(); + String intermediateModelId = intermediatePath.toString(); // intermediateModelURI.trimFileExtension().lastSegment(); + + if (gipslModelId.equalsIgnoreCase(intermediateModelId)) + throw new IllegalArgumentException( + "GIPSL and Intermediate model id should not be equal: '" + gipslModelId + "'"); + + TraceMap gipsl2intermediateMppings = TraceMap.normalize(transformer.getTrace(), + ResolveEcore2Id.INSTANCE, ResolveEcore2Id.INSTANCE); + + traceContext + .updateTraceModel(new TraceModelLink(gipslModelId, intermediateModelId, gipsl2intermediateMppings)); + } catch (Exception e) { + e.printStackTrace(); + } + } + /** * Gets the class name prefix for a given project * diff --git a/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsProjectBuilder.java b/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsProjectBuilder.java index d64f7eae..e7fe2c63 100644 --- a/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsProjectBuilder.java +++ b/org.emoflon.gips.build/src/org/emoflon/gips/build/GipsProjectBuilder.java @@ -73,6 +73,10 @@ public void build(IProject project, Resource resource) { GipsBuilderUtils.saveResource(model, gipsApiData.apiPackageFolder.getLocation() + "/gips/gips-model.xmi"); gipsApiData.intermediateModelURI = URI.createPlatformResourceURI( gipsApiData.apiPackageFolder.getProjectRelativePath() + "/gips/gips-model.xmi", true); + // FIXME; createPlatformResourceURI requires a path of the form + // "/project-name/path", but getProjectRelativePath() does not include + // "project-name" + // TODO: See if this can be easily fixed. // build HiPE engine code if (ibexModel != null && !ibexModel.getPatternSet().getContextPatterns().isEmpty()) { @@ -99,6 +103,12 @@ public void build(IProject project, Resource resource) { // generate files GipsCodeGenerator gipsGen = new GipsCodeGenerator(model, gipsApiData, manager); gipsGen.generate(); + + // update gipsl->intermediate tracing + LogUtils.info(logger, "GipsProjectBuilder: update trace..."); + GipsBuilderUtils.updateTrace(project, gipsSlangFile.eResource().getURI(), gipsApiData.intermediateModelURI, + transformer); + LogUtils.info(logger, "GipsProjectBuilder: Done!"); try { @@ -108,5 +118,4 @@ public void build(IProject project, Resource resource) { e.printStackTrace(); } } - } diff --git a/org.emoflon.gips.build/src/org/emoflon/gips/build/transformation/GipsToIntermediate.java b/org.emoflon.gips.build/src/org/emoflon/gips/build/transformation/GipsToIntermediate.java index 72f182fa..aabb1e69 100644 --- a/org.emoflon.gips.build/src/org/emoflon/gips/build/transformation/GipsToIntermediate.java +++ b/org.emoflon.gips.build/src/org/emoflon/gips/build/transformation/GipsToIntermediate.java @@ -17,6 +17,7 @@ import org.emoflon.gips.build.transformation.transformer.ArithmeticExpressionTransformer; import org.emoflon.gips.build.transformation.transformer.BooleanExpressionTransformer; import org.emoflon.gips.build.transformation.transformer.TransformerFactory; +import org.emoflon.gips.debugger.trace.TraceMap; import org.emoflon.gips.gipsl.gipsl.EditorGTFile; import org.emoflon.gips.gipsl.gipsl.GipsArithmeticExpression; import org.emoflon.gips.gipsl.gipsl.GipsBooleanExpression; @@ -76,6 +77,7 @@ public class GipsToIntermediate { protected GipsIntermediateFactory factory = GipsIntermediateFactory.eINSTANCE; final protected GipsTransformationData data; final protected TransformerFactory transformationFactory; + final private TraceMap gipsl2gipsTrace = new TraceMap<>(); protected int constraintCounter = 0; public GipsToIntermediate(final EditorGTFile gipslFile) { @@ -131,6 +133,10 @@ public GipsIntermediateModel transform() throws Exception { return data.model(); } + public TraceMap getTrace() { + return gipsl2gipsTrace; + } + protected void preprocessGipslFile() { EditorToIBeXPatternTransformation ibexTransformer = new EditorToIBeXPatternTransformation(); data.model().setIbexModel(ibexTransformer.transform(data.gipslFile())); @@ -218,6 +224,7 @@ protected void transformConfig() { } data.model().setConfig(config); + gipsl2gipsTrace.map(eConfig, config); } protected void transformMappings() { @@ -254,6 +261,7 @@ protected void transformMappings() { data.model().getMappings().add(mapping); data.eMapping2Mapping().put(eMapping, mapping); + gipsl2gipsTrace.map(eMapping, mapping); }); } @@ -286,6 +294,7 @@ protected void transformMappingVariables(GipsMapping mapping, RuleMapping ruleMa ruleMapping.getBoundVariables().add(var); data.model().getVariables().add(var); data.eVariable2Variable().put(gipsVar, var); + gipsl2gipsTrace.map(gipsVar, var); } else { Variable var = factory.createVariable(); var.setType(GipsTransformationUtils.typeToVariableType(gipsVar.getType())); @@ -295,6 +304,7 @@ protected void transformMappingVariables(GipsMapping mapping, RuleMapping ruleMa ruleMapping.getFreeVariables().add(var); data.model().getVariables().add(var); data.eVariable2Variable().put(gipsVar, var); + gipsl2gipsTrace.map(gipsVar, var); } } } @@ -312,6 +322,7 @@ protected void transformMappingVariables(GipsMapping mapping, PatternMapping pat patternMapping.getFreeVariables().add(var); data.model().getVariables().add(var); data.eVariable2Variable().put(gipsVar, var); + gipsl2gipsTrace.map(gipsVar, var); } } @@ -350,6 +361,7 @@ protected void transformConstraints() throws Exception { case DISJUNCTION_OF_LITERALS -> { Constraint disjunction = createDisjunctConstraint(eSubConstraint, constraintCounter); data.model().getConstraints().add(disjunction); + gipsl2gipsTrace.map(eConstraint, disjunction); constraintCounter++; Map> transformed = new HashMap<>(); @@ -363,6 +375,7 @@ protected void transformConstraints() throws Exception { symbolicVariable); constraints.forEach(c -> c.setSymbolicVariable(symbolicVariable)); transformed.put(subConstraint, constraints); + gipsl2gipsTrace.mapOneToMany(eConstraint, constraints); disjunction.getDependencies().addAll(constraints); } @@ -442,12 +455,17 @@ protected void transformConstraints() throws Exception { substituteRelation.setLhs(substituteSum); disjunction.setExpression(substituteRelation); + gipsl2gipsTrace.map(eConstraint.getExpression(), disjunction.getExpression()); } case LITERAL -> { - transformConstraint(eSubConstraint.result().values().iterator().next(), false, null); + Collection constraints = transformConstraint( + eSubConstraint.result().values().iterator().next(), false, null); + gipsl2gipsTrace.mapOneToMany(eConstraint, constraints); } case NEGATED_LITERAL -> { - transformConstraint(eSubConstraint.result().values().iterator().next(), true, null); + Collection constraints = transformConstraint( + eSubConstraint.result().values().iterator().next(), true, null); + gipsl2gipsTrace.mapOneToMany(eConstraint, constraints); } default -> { throw new IllegalArgumentException("Unknown constraint annotation type."); @@ -519,6 +537,7 @@ protected void transformLinearFunctions() throws Exception { LinearFunction linearFunction = createLinearFunction(eLinearFunction); data.model().getFunctions().add(linearFunction); data.eFunction2Function().put(eLinearFunction, linearFunction); + gipsl2gipsTrace.map(eLinearFunction, linearFunction); // Create all constants if (eLinearFunction.getConstants() != null && !eLinearFunction.getConstants().isEmpty()) { @@ -543,6 +562,7 @@ protected void transformLinearFunctions() throws Exception { // of products. linearFunction.setExpression( new GipsArithmeticTransformer(factory).normalizeAndExpand(linearFunction.getExpression())); + gipsl2gipsTrace.map(eLinearFunction.getExpression(), linearFunction.getExpression()); } } @@ -554,6 +574,7 @@ protected void transformObjective() throws Exception { Objective objective = factory.createObjective(); data.model().setObjective(objective); + gipsl2gipsTrace.map(eObjective, objective); // Create all constants if (eObjective.getConstants() != null && !eObjective.getConstants().isEmpty()) { @@ -588,6 +609,7 @@ protected void transformObjective() throws Exception { // Rewrite the expression, which will be translated into ILP-Terms, into a sum // of products. objective.setExpression(new GipsArithmeticTransformer(factory).normalizeAndExpand(objective.getExpression())); + gipsl2gipsTrace.map(eObjective.getExpression(), objective.getExpression()); } protected Constraint createConstraint(final GipsConstraint eConstraint, int counter) { @@ -782,6 +804,7 @@ protected void mapGT2IBeXElements() { for (IBeXRule rule : data.model().getIbexModel().getRuleSet().getRules()) { if (rule.getName().equals(ePattern.getName())) { data.addRule(ePattern, rule); + gipsl2gipsTrace.map(ePattern, rule); } } } @@ -795,10 +818,12 @@ protected void mapGT2IBeXElements() { for (IBeXContext pattern : data.model().getIbexModel().getPatternSet().getContextPatterns()) { if (pattern.getName().equals(ePattern.getName())) { data.addPattern(ePattern, pattern); + gipsl2gipsTrace.map(ePattern, pattern); for (EditorNode eNode : ePattern.getNodes()) { for (IBeXNode node : toContextPattern(pattern).getSignatureNodes()) { if (eNode.getName().equals(node.getName())) { data.eNode2Node().putIfAbsent(eNode, node); + gipsl2gipsTrace.map(eNode, node); } } } diff --git a/org.emoflon.gips.core/META-INF/MANIFEST.MF b/org.emoflon.gips.core/META-INF/MANIFEST.MF index efe52034..5d4209c8 100644 --- a/org.emoflon.gips.core/META-INF/MANIFEST.MF +++ b/org.emoflon.gips.core/META-INF/MANIFEST.MF @@ -7,8 +7,9 @@ Automatic-Module-Name: org.emoflon.gips.core Bundle-RequiredExecutionEnvironment: JavaSE-21 Require-Bundle: org.emoflon.ibex.gt;visibility:=reexport, org.emoflon.gips.intermediate;visibility:=reexport, - org.emoflon.gips.core.dependencies;visibility:=reexport, - org.emoflon.gips.build + org.emoflon.gips.core.dependencies;bundle-version="1.0.0";visibility:=reexport, + org.emoflon.gips.build, + org.emoflon.gips.debugger;bundle-version="[1.0.0,2.0.0)";visibility:=reexport Export-Package: org.emoflon.gips.core, org.emoflon.gips.core.api, org.emoflon.gips.core.gt, diff --git a/org.emoflon.gips.core/src/org/emoflon/gips/core/GipsEngine.java b/org.emoflon.gips.core/src/org/emoflon/gips/core/GipsEngine.java index 0ddadd53..ed3499d8 100644 --- a/org.emoflon.gips.core/src/org/emoflon/gips/core/GipsEngine.java +++ b/org.emoflon.gips.core/src/org/emoflon/gips/core/GipsEngine.java @@ -8,9 +8,13 @@ import org.emoflon.gips.core.milp.Solver; import org.emoflon.gips.core.milp.SolverOutput; import org.emoflon.gips.core.milp.SolverStatus; +import org.emoflon.gips.core.milp.model.Constraint; +import org.emoflon.gips.core.milp.model.Term; import org.emoflon.gips.core.milp.model.Variable; import org.emoflon.gips.core.util.Observer; import org.emoflon.gips.core.validation.GipsConstraintValidationLog; +import org.emoflon.gips.debugger.api.ILPTraceKeywords; +import org.emoflon.gips.debugger.api.Intermediate2IlpTracer; public abstract class GipsEngine { @@ -24,6 +28,7 @@ public abstract class GipsEngine { final protected Map> functions = Collections.synchronizedMap(new HashMap<>()); protected GipsObjective objective; protected Solver solver; + final protected Intermediate2IlpTracer tracer = new Intermediate2IlpTracer(); public abstract void update(); @@ -108,6 +113,52 @@ public void buildProblem(boolean doUpdate) { objective.buildObjectiveFunction(); solver.buildILPProblem(); + buildTracingTree(); + } + + protected void buildTracingTree() { + Intermediate2IlpTracer tracer = getTracer(); + if (!tracer.isTracingEnabled()) + return; + + // try to build a bridge between ILP model and ILP text file + + for (GipsMapper mapper : this.mappers.values()) { + tracer.map(mapper.mapping, + ILPTraceKeywords.buildElementId(ILPTraceKeywords.TYPE_MAPPING, mapper.getName())); +// final var fragmentPath = EcoreUtil.getURI(mapper.mapping); + } + + for (GipsConstraint constraint : this.constraints.values()) { + tracer.map(constraint.constraint, + ILPTraceKeywords.buildElementId(ILPTraceKeywords.TYPE_CONSTRAINT, constraint.getName())); + for (Constraint ilpConstraint : constraint.getConstraints()) { + for (Term ilpTerm : ilpConstraint.lhsTerms()) { + tracer.map(constraint.constraint.getExpression(), ILPTraceKeywords + .buildElementId(ILPTraceKeywords.TYPE_CONSTRAINT_VAR, ilpTerm.variable().getName())); + } + } + + for (Variable variables : constraint.getAdditionalVariables()) { + tracer.map(constraint.constraint.getExpression(), + ILPTraceKeywords.buildElementId(ILPTraceKeywords.TYPE_CONSTRAINT_VAR, variables.getName())); + } + } + + for (GipsLinearFunction function : this.functions.values()) { + tracer.map(function.linearFunction, + ILPTraceKeywords.buildElementId(ILPTraceKeywords.TYPE_FUNCTION, function.getName())); + for (Term term : function.terms) { + tracer.map(function.linearFunction, + ILPTraceKeywords.buildElementId(ILPTraceKeywords.TYPE_FUNCTION_VAR, term.variable().getName())); + } + } + + if (objective != null) { + tracer.map(objective.objective, ILPTraceKeywords.buildElementId(ILPTraceKeywords.TYPE_OBJECTIVE, "")); + } + + tracer.postTraceToRMIService(); } public SolverOutput solveProblemTimed() { @@ -233,4 +284,8 @@ protected void setObjective(final GipsObjective objective) { public void setSolver(final Solver solver) { this.solver = solver; } + + public Intermediate2IlpTracer getTracer() { + return this.tracer; + } } diff --git a/org.emoflon.gips.core/src/org/emoflon/gips/core/api/GipsEngineAPI.java b/org.emoflon.gips.core/src/org/emoflon/gips/core/api/GipsEngineAPI.java index 8e56c23e..74238cec 100644 --- a/org.emoflon.gips.core/src/org/emoflon/gips/core/api/GipsEngineAPI.java +++ b/org.emoflon.gips.core/src/org/emoflon/gips/core/api/GipsEngineAPI.java @@ -167,6 +167,7 @@ public ResourceSet getResourceSet() { * Terminates the GipsEngine (super class) and the eMoflon::IBeX engine * (including the pattern matcher). */ + @Override public void terminate() { // Terminate the GipsEngine super.terminate(); @@ -292,6 +293,7 @@ private void initInternalCommon(final URI gipsModelURI) { initTypeIndexer(); validationLog = new GipsConstraintValidationLog(); setSolverConfig(gipsModel.getConfig()); + initTracer(); initMapperFactory(); createMappers(); initConstraintFactory(); @@ -327,6 +329,11 @@ protected void loadIntermediateModel(final URI gipsModelURI) { gipsModel.getMappings().forEach(mapping -> name2Mapping.put(mapping.getName(), mapping)); } + protected void initTracer() { + tracer.computeGipsModelId(gipsModel.eResource().getURI()); + tracer.computeLpModelId(gipsModel.getConfig().getLpPath()); + } + protected abstract void createMappers(); protected void createConstraints() { diff --git a/org.emoflon.gips.debugger.cplexlp.ide/.classpath b/org.emoflon.gips.debugger.cplexlp.ide/.classpath new file mode 100644 index 00000000..0c116be1 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ide/.classpath @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.cplexlp.ide/.gitignore b/org.emoflon.gips.debugger.cplexlp.ide/.gitignore new file mode 100644 index 00000000..95c365bf --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ide/.gitignore @@ -0,0 +1,63 @@ +.metadata +bin/ +test-bin/ +src-gen/ +xtend-gen/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project \ No newline at end of file diff --git a/org.emoflon.gips.debugger.cplexlp.ide/.project b/org.emoflon.gips.debugger.cplexlp.ide/.project new file mode 100644 index 00000000..5a088693 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ide/.project @@ -0,0 +1,34 @@ + + + org.emoflon.gips.debugger.cplexlp.ide + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.xtext.ui.shared.xtextNature + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/org.emoflon.gips.debugger.cplexlp.ide/META-INF/MANIFEST.MF b/org.emoflon.gips.debugger.cplexlp.ide/META-INF/MANIFEST.MF new file mode 100644 index 00000000..9bda00d1 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ide/META-INF/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Automatic-Module-Name: org.emoflon.gips.debugger.cplexlp.ide +Bundle-ManifestVersion: 2 +Bundle-Name: Gips::Debugger::CplexLp::IDE +Bundle-Vendor: Real-Time Systems Lab - TU Darmstadt +Bundle-Version: 1.0.0.qualifier +Bundle-SymbolicName: org.emoflon.gips.debugger.cplexlp.ide; singleton:=true +Bundle-ActivationPolicy: lazy +Require-Bundle: org.emoflon.gips.debugger.cplexlp, + org.eclipse.xtext.ide, + org.eclipse.xtext.xbase.ide, + org.antlr.runtime;bundle-version="[3.2.0,3.2.1)" +Bundle-RequiredExecutionEnvironment: JavaSE-21 +Export-Package: org.emoflon.gips.debugger.ide.contentassist.antlr.lexer, + org.emoflon.gips.debugger.ide.contentassist.antlr, + org.emoflon.gips.debugger.ide.contentassist.antlr.internal diff --git a/org.emoflon.gips.debugger.cplexlp.ide/build.properties b/org.emoflon.gips.debugger.cplexlp.ide/build.properties new file mode 100644 index 00000000..5c6bbf99 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ide/build.properties @@ -0,0 +1,6 @@ +source.. = src/,\ + src-gen/,\ + xtend-gen/ +bin.includes = .,\ + META-INF/ +bin.excludes = **/*.xtend diff --git a/org.emoflon.gips.debugger.cplexlp.ide/src/org/emoflon/gips/debugger/ide/CplexLpIdeModule.java b/org.emoflon.gips.debugger.cplexlp.ide/src/org/emoflon/gips/debugger/ide/CplexLpIdeModule.java new file mode 100644 index 00000000..7078a5f8 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ide/src/org/emoflon/gips/debugger/ide/CplexLpIdeModule.java @@ -0,0 +1,11 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ide; + + +/** + * Use this class to register ide components. + */ +public class CplexLpIdeModule extends AbstractCplexLpIdeModule { +} diff --git a/org.emoflon.gips.debugger.cplexlp.ide/src/org/emoflon/gips/debugger/ide/CplexLpIdeSetup.java b/org.emoflon.gips.debugger.cplexlp.ide/src/org/emoflon/gips/debugger/ide/CplexLpIdeSetup.java new file mode 100644 index 00000000..604c2dde --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ide/src/org/emoflon/gips/debugger/ide/CplexLpIdeSetup.java @@ -0,0 +1,22 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ide; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.eclipse.xtext.util.Modules2; +import org.emoflon.gips.debugger.CplexLpRuntimeModule; +import org.emoflon.gips.debugger.CplexLpStandaloneSetup; + +/** + * Initialization support for running Xtext languages as language servers. + */ +public class CplexLpIdeSetup extends CplexLpStandaloneSetup { + + @Override + public Injector createInjector() { + return Guice.createInjector(Modules2.mixin(new CplexLpRuntimeModule(), new CplexLpIdeModule())); + } + +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/.classpath b/org.emoflon.gips.debugger.cplexlp.ui/.classpath new file mode 100644 index 00000000..a07cfd69 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/.classpath @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.cplexlp.ui/.gitignore b/org.emoflon.gips.debugger.cplexlp.ui/.gitignore new file mode 100644 index 00000000..a9900a4f --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/.gitignore @@ -0,0 +1,63 @@ +.metadata +bin/ +test-bin/ +xtend-gen/ +src-gen/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project \ No newline at end of file diff --git a/org.emoflon.gips.debugger.cplexlp.ui/.project b/org.emoflon.gips.debugger.cplexlp.ui/.project new file mode 100644 index 00000000..1ea704dd --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/.project @@ -0,0 +1,34 @@ + + + org.emoflon.gips.debugger.cplexlp.ui + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.xtext.ui.shared.xtextNature + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/org.emoflon.gips.debugger.cplexlp.ui/META-INF/MANIFEST.MF b/org.emoflon.gips.debugger.cplexlp.ui/META-INF/MANIFEST.MF new file mode 100644 index 00000000..2a33103c --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/META-INF/MANIFEST.MF @@ -0,0 +1,25 @@ +Manifest-Version: 1.0 +Automatic-Module-Name: org.emoflon.gips.debugger.cplexlp.ui +Bundle-ManifestVersion: 2 +Bundle-Name: Gips::Debugger::CplexLp::UI +Bundle-Vendor: Real-Time Systems Lab - TU Darmstadt +Bundle-Version: 1.0.0.qualifier +Bundle-SymbolicName: org.emoflon.gips.debugger.cplexlp.ui; singleton:=true +Bundle-ActivationPolicy: lazy +Require-Bundle: org.emoflon.gips.debugger.cplexlp, + org.emoflon.gips.debugger.cplexlp.ide, + org.eclipse.xtext.ui, + org.eclipse.xtext.ui.shared, + org.eclipse.xtext.ui.codetemplates.ui, + org.eclipse.ui.editors;bundle-version="3.14.300", + org.eclipse.ui.ide;bundle-version="3.18.500", + org.eclipse.ui, + org.eclipse.compare, + org.eclipse.xtext.builder, + org.eclipse.xtext.xbase.ui +Import-Package: org.apache.log4j +Bundle-RequiredExecutionEnvironment: JavaSE-21 +Export-Package: org.emoflon.gips.debugger.ui.quickfix, + org.emoflon.gips.debugger.ui.contentassist, + org.emoflon.gips.debugger.cplexlp.ui.internal +Bundle-Activator: org.emoflon.gips.debugger.cplexlp.ui.internal.CplexlpActivator diff --git a/org.emoflon.gips.debugger.cplexlp.ui/build.properties b/org.emoflon.gips.debugger.cplexlp.ui/build.properties new file mode 100644 index 00000000..323f56c5 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/build.properties @@ -0,0 +1,7 @@ +source.. = src/,\ + src-gen/,\ + xtend-gen/ +bin.includes = .,\ + META-INF/,\ + plugin.xml +bin.excludes = **/*.xtend diff --git a/org.emoflon.gips.debugger.cplexlp.ui/plugin.xml b/org.emoflon.gips.debugger.cplexlp.ui/plugin.xml new file mode 100644 index 00000000..f3f78fe0 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/plugin.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/CplexLpUiModule.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/CplexLpUiModule.java new file mode 100644 index 00000000..5581bd50 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/CplexLpUiModule.java @@ -0,0 +1,43 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ui; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.eclipse.xtext.ide.editor.syntaxcoloring.ISemanticHighlightingCalculator; +import org.emoflon.gips.debugger.ui.editor.CplexLpEditor; +import org.emoflon.gips.debugger.ui.editor.preferences.ClplexLpPreferences; +import org.emoflon.gips.debugger.ui.highlight.ClplexLpHighlightingCalculator; +import org.emoflon.gips.debugger.ui.highlight.ClplexLpHighlightingConfiguration; + +/** + * Use this class to register components to be used within the Eclipse IDE. + */ +public class CplexLpUiModule extends AbstractCplexLpUiModule { + + public CplexLpUiModule(AbstractUIPlugin plugin) { + super(plugin); + } + + public Class bindXtextEditor() { + return CplexLpEditor.class; + } + + public Class bindLanguageRootPreferencePage() { + return ClplexLpPreferences.class; + } + + public Class bindIHighlightingConfiguration() { + return ClplexLpHighlightingConfiguration.class; + } + + public Class bindISemanticHighlightingCalculator() { + return ClplexLpHighlightingCalculator.class; + } + +// public void configureResourceUIServiceLabelProvider(Binder binder) { +// binder.bind(ILabelProvider.class).annotatedWith(ResourceServiceDescriptionLabelProvider.class) +// .to(CplexLpDescriptionLabelProvider.class); +// } + +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/contentassist/CplexLpProposalProvider.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/contentassist/CplexLpProposalProvider.java new file mode 100644 index 00000000..e51db456 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/contentassist/CplexLpProposalProvider.java @@ -0,0 +1,12 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ui.contentassist; + + +/** + * See https://www.eclipse.org/Xtext/documentation/310_eclipse_support.html#content-assist + * on how to customize the content assistant. + */ +public class CplexLpProposalProvider extends AbstractCplexLpProposalProvider { +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/editor/CplexLpEditor.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/editor/CplexLpEditor.java new file mode 100644 index 00000000..8a8ade3e --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/editor/CplexLpEditor.java @@ -0,0 +1,26 @@ +package org.emoflon.gips.debugger.ui.editor; + +import org.eclipse.xtext.ui.editor.XtextEditor; + +public class CplexLpEditor extends XtextEditor { + +// @Inject +// private XbaseEditorInputRedirector editorInputRedirector; + + public CplexLpEditor() { + super(); + } + +// @Override +// protected void doSetInput(IEditorInput input) throws CoreException { +// try { +// IEditorInput inputToUse = editorInputRedirector.findOriginalSource(input); +// super.doSetInput(inputToUse); +// return; +// } catch (CoreException e) { +// // ignore +// } +// super.doSetInput(input); +// } + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/editor/preferences/ClplexLpPreferences.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/editor/preferences/ClplexLpPreferences.java new file mode 100644 index 00000000..94d0c746 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/editor/preferences/ClplexLpPreferences.java @@ -0,0 +1,13 @@ +package org.emoflon.gips.debugger.ui.editor.preferences; + +import org.eclipse.xtext.ui.editor.preferences.LanguageRootPreferencePage; + +// https://stackoverflow.com/questions/7964212/correctly-initializing-and-retrieving-preferences-in-a-xtext-based-eclipse-plugi +public class ClplexLpPreferences extends LanguageRootPreferencePage { + +// @Override +// public void init(IWorkbench workbench) { +// setPreferenceStore(Activator.getInstance().getPreferenceStore()); +// } + +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/highlight/ClplexLpHighlightingCalculator.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/highlight/ClplexLpHighlightingCalculator.java new file mode 100644 index 00000000..a40c2743 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/highlight/ClplexLpHighlightingCalculator.java @@ -0,0 +1,42 @@ +package org.emoflon.gips.debugger.ui.highlight; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.ide.editor.syntaxcoloring.DefaultSemanticHighlightingCalculator; +import org.eclipse.xtext.ide.editor.syntaxcoloring.HighlightingStyles; +import org.eclipse.xtext.ide.editor.syntaxcoloring.IHighlightedPositionAcceptor; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.ILeafNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.util.CancelIndicator; +import org.emoflon.gips.debugger.services.CplexLpGrammarAccess; + +import com.google.inject.Inject; + +public class ClplexLpHighlightingCalculator extends DefaultSemanticHighlightingCalculator { + + public static class ClplexLpHighlightingStyles implements HighlightingStyles { + public static String KEYWORD_CONSTRAINT = "Constraint"; + } + + @Inject + private CplexLpGrammarAccess grammarAccess; + + @Override + protected boolean highlightElement(EObject object, IHighlightedPositionAcceptor acceptor, + CancelIndicator cancelIndicator) { + if (object instanceof org.emoflon.gips.debugger.cplexLp.ConstraintExpression) { + Keyword colonKey = grammarAccess.getConstraintExpressionAccess().getColonKeyword_0_1(); + ICompositeNode textNode = NodeModelUtils.findActualNodeFor(object); + for (ILeafNode node : textNode.getLeafNodes()) { + if (colonKey == node.getGrammarElement()) { + var start = textNode.getOffset(); + var length = node.getEndOffset() - start; + acceptor.addPosition(start, length, ClplexLpHighlightingStyles.KEYWORD_CONSTRAINT); + } + } + } + return super.highlightElement(object, acceptor, cancelIndicator); + } + +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/highlight/ClplexLpHighlightingConfiguration.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/highlight/ClplexLpHighlightingConfiguration.java new file mode 100644 index 00000000..6760088f --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/highlight/ClplexLpHighlightingConfiguration.java @@ -0,0 +1,27 @@ +package org.emoflon.gips.debugger.ui.highlight; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.xtext.ui.editor.syntaxcoloring.DefaultHighlightingConfiguration; +import org.eclipse.xtext.ui.editor.syntaxcoloring.IHighlightingConfigurationAcceptor; +import org.eclipse.xtext.ui.editor.utils.TextStyle; +import org.emoflon.gips.debugger.ui.highlight.ClplexLpHighlightingCalculator.ClplexLpHighlightingStyles; + +public class ClplexLpHighlightingConfiguration extends DefaultHighlightingConfiguration { + + @Override + public void configure(IHighlightingConfigurationAcceptor acceptor) { + // externalize + acceptor.acceptDefaultHighlighting(ClplexLpHighlightingStyles.KEYWORD_CONSTRAINT, "Constraints", + constraintTextStyle()); + super.configure(acceptor); + } + + public TextStyle constraintTextStyle() { + TextStyle textStyle = defaultTextStyle().copy(); + textStyle.setColor(new RGB(0, 141, 137)); + textStyle.setStyle(SWT.BOLD); + return textStyle; + } + +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/labeling/CplexLpDescriptionLabelProvider.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/labeling/CplexLpDescriptionLabelProvider.java new file mode 100644 index 00000000..4de3c0c3 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/labeling/CplexLpDescriptionLabelProvider.java @@ -0,0 +1,25 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ui.labeling; + +import org.eclipse.xtext.ui.label.DefaultDescriptionLabelProvider; + +/** + * Provides labels for IEObjectDescriptions and IResourceDescriptions. + * + * See https://www.eclipse.org/Xtext/documentation/310_eclipse_support.html#label-provider + */ +public class CplexLpDescriptionLabelProvider extends DefaultDescriptionLabelProvider { + + // Labels and icons can be computed like this: +// @Override +// public String text(IEObjectDescription ele) { +// return ele.getName().toString(); +// } +// +// @Override +// public String image(IEObjectDescription ele) { +// return ele.getEClass().getName() + ".gif"; +// } +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/labeling/CplexLpLabelProvider.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/labeling/CplexLpLabelProvider.java new file mode 100644 index 00000000..e92d4930 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/labeling/CplexLpLabelProvider.java @@ -0,0 +1,168 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ui.labeling; + +import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; +import org.eclipse.xtext.ui.label.DefaultEObjectLabelProvider; +import org.emoflon.gips.debugger.cplexLp.ConstraintExpression; +import org.emoflon.gips.debugger.cplexLp.EqualsToBoundExpression; +import org.emoflon.gips.debugger.cplexLp.FreeBoundExpression; +import org.emoflon.gips.debugger.cplexLp.Infinity; +import org.emoflon.gips.debugger.cplexLp.InfinityLiteral; +import org.emoflon.gips.debugger.cplexLp.LinearExpression; +import org.emoflon.gips.debugger.cplexLp.LinearOperators; +import org.emoflon.gips.debugger.cplexLp.LinearTerm; +import org.emoflon.gips.debugger.cplexLp.LowerBoundExpression; +import org.emoflon.gips.debugger.cplexLp.NumberLiteral; +import org.emoflon.gips.debugger.cplexLp.SectionBinaryValue; +import org.emoflon.gips.debugger.cplexLp.SectionBound; +import org.emoflon.gips.debugger.cplexLp.SectionConstraint; +import org.emoflon.gips.debugger.cplexLp.SectionIntegerValue; +import org.emoflon.gips.debugger.cplexLp.SectionObjective; +import org.emoflon.gips.debugger.cplexLp.UpperAndLowerBoundExpression; +import org.emoflon.gips.debugger.cplexLp.UpperBoundExpression; +import org.emoflon.gips.debugger.cplexLp.VariableDecleration; +import org.emoflon.gips.debugger.cplexLp.VariableRef; + +import com.google.inject.Inject; + +/** + * Provides labels for EObjects. + * + * See + * https://www.eclipse.org/Xtext/documentation/310_eclipse_support.html#label-provider + */ +public class CplexLpLabelProvider extends DefaultEObjectLabelProvider { + + @Inject + public CplexLpLabelProvider(AdapterFactoryLabelProvider delegate) { + super(delegate); + } + + String text(SectionObjective element) { + return "Goal: " + element.getGoal(); + } + + String text(SectionConstraint element) { + return "Constraints"; + } + + String text(SectionBound element) { + return "Bounds"; + } + + String text(SectionBinaryValue element) { + return "Binaries"; + } + + String text(SectionIntegerValue element) { + return "Generals"; + } + + String text(ConstraintExpression element) { + var builder = new StringBuilder(); + if (element.getName() != null) { + builder.append(element.getName()); + } else { + builder.append("constraint"); + } + return builder.toString(); + } + + String text(UpperAndLowerBoundExpression element) { + var builder = new StringBuilder(); + builder.append(doGetText(element.getLowerBound())); + builder.append(" <= "); + builder.append(doGetText(element.getVariable())); + builder.append(" <= "); + builder.append(doGetText(element.getUpperBound())); + return builder.toString(); + } + + String text(UpperBoundExpression element) { + var builder = new StringBuilder(); + builder.append(doGetText(element.getVariable())); + builder.append(" <= "); + builder.append(doGetText(element.getUpperBound())); + return builder.toString(); + } + + String text(LowerBoundExpression element) { + var builder = new StringBuilder(); + builder.append(doGetText(element.getLowerBound())); + builder.append(" <= "); + builder.append(doGetText(element.getVariable())); + return builder.toString(); + } + + String text(FreeBoundExpression element) { + var builder = new StringBuilder(); + builder.append(doGetText(element.getVariable())); + builder.append(" free"); + return builder.toString(); + } + + String text(EqualsToBoundExpression element) { + var builder = new StringBuilder(); + builder.append(doGetText(element.getVariable())); + builder.append(" = "); + builder.append(doGetText(element.getBound())); + return builder.toString(); + } + + String text(LinearExpression element) { + var builder = new StringBuilder(); + for (var e : element.getTerms()) { + builder.append(text(e)); + if (builder.length() > 80) { + builder.setLength(76); + builder.append(" ..."); + break; + } + } + return builder.toString(); + } + + String text(LinearTerm element) { + var builder = new StringBuilder(); + if (element.getOperator() != null) { + builder.append(text(element.getOperator())); + } + if (element.getCoefficient() != null) { + builder.append(doGetText(element.getCoefficient())); + } + if (element.getCoefficient() != null && element.getVariable() != null) { + builder.append("*"); + } + if (element.getVariable() != null) { + builder.append(doGetText(element.getVariable())); + } + return builder.toString(); + } + + String text(NumberLiteral element) { + return element.getValue(); + } + + String text(InfinityLiteral element) { + return text(element.getValue()); + } + + String text(VariableDecleration element) { + return element.getName(); + } + + String text(VariableRef element) { + return element.getRef().getName(); + } + + String text(Infinity element) { + return element.toString(); + } + + String text(LinearOperators element) { + return element.toString(); + } + +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/outline/CplexLpOutlineTreeProvider.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/outline/CplexLpOutlineTreeProvider.java new file mode 100644 index 00000000..088bd495 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/outline/CplexLpOutlineTreeProvider.java @@ -0,0 +1,53 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ui.outline; + +import org.eclipse.xtext.ui.editor.outline.impl.DefaultOutlineTreeProvider; +import org.emoflon.gips.debugger.cplexLp.BoundExpression; +import org.emoflon.gips.debugger.cplexLp.ConstraintExpression; +import org.emoflon.gips.debugger.cplexLp.LinearTerm; +import org.emoflon.gips.debugger.cplexLp.SectionObjective; + +/** + * Customization of the default outline structure. + * + * See + * https://www.eclipse.org/Xtext/documentation/310_eclipse_support.html#outline + */ +public class CplexLpOutlineTreeProvider extends DefaultOutlineTreeProvider { + + public boolean _isLeaf(SectionObjective element) { + return true; + } + + public boolean _isLeaf(ConstraintExpression element) { + return true; + } + + public boolean _isLeaf(BoundExpression element) { + return true; + } + + public boolean _isLeaf(LinearTerm element) { + return true; + } + +// @Override +// public void createChildren(IOutlineNode parent, EObject modelElement) { +// try { +// internalCreateChildren(parent, modelElement); +// } catch(Exception e) { +// e.printStackTrace(); +// super.createChildren(parent, modelElement); +// } +// } +// +// public void internalCreateChildren(IOutlineNode parent, EObject modelElement) throws Exception{ +// if(modelElement instanceof GipsConstraint constr) { +// createEObjectNode(parent, constr, null, "Constraint", false); +// } else { +// super.createChildren(parent, modelElement); +// } +// } +} diff --git a/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/quickfix/CplexLpQuickfixProvider.java b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/quickfix/CplexLpQuickfixProvider.java new file mode 100644 index 00000000..c3dc6736 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp.ui/src/org/emoflon/gips/debugger/ui/quickfix/CplexLpQuickfixProvider.java @@ -0,0 +1,26 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.ui.quickfix; + +import org.eclipse.xtext.ui.editor.quickfix.DefaultQuickfixProvider; + +/** + * Custom quickfixes. + * + * See https://www.eclipse.org/Xtext/documentation/310_eclipse_support.html#quick-fixes + */ +public class CplexLpQuickfixProvider extends DefaultQuickfixProvider { + +// @Fix(CplexLpValidator.INVALID_NAME) +// public void capitalizeName(final Issue issue, IssueResolutionAcceptor acceptor) { +// acceptor.accept(issue, "Capitalize name", "Capitalize the name.", "upcase.png", new IModification() { +// public void apply(IModificationContext context) throws BadLocationException { +// IXtextDocument xtextDocument = context.getXtextDocument(); +// String firstLetter = xtextDocument.get(issue.getOffset(), 1); +// xtextDocument.replace(issue.getOffset(), 1, firstLetter.toUpperCase()); +// } +// }); +// } + +} diff --git a/org.emoflon.gips.debugger.cplexlp/.antlr-generator-3.2.0-patch.jar b/org.emoflon.gips.debugger.cplexlp/.antlr-generator-3.2.0-patch.jar new file mode 100644 index 00000000..90516fd7 Binary files /dev/null and b/org.emoflon.gips.debugger.cplexlp/.antlr-generator-3.2.0-patch.jar differ diff --git a/org.emoflon.gips.debugger.cplexlp/.classpath b/org.emoflon.gips.debugger.cplexlp/.classpath new file mode 100644 index 00000000..a07cfd69 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/.classpath @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.cplexlp/.gitignore b/org.emoflon.gips.debugger.cplexlp/.gitignore new file mode 100644 index 00000000..95c365bf --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/.gitignore @@ -0,0 +1,63 @@ +.metadata +bin/ +test-bin/ +src-gen/ +xtend-gen/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project \ No newline at end of file diff --git a/org.emoflon.gips.debugger.cplexlp/.project b/org.emoflon.gips.debugger.cplexlp/.project new file mode 100644 index 00000000..4a6b6115 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/.project @@ -0,0 +1,34 @@ + + + org.emoflon.gips.debugger.cplexlp + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.xtext.ui.shared.xtextNature + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/org.emoflon.gips.debugger.cplexlp/META-INF/MANIFEST.MF b/org.emoflon.gips.debugger.cplexlp/META-INF/MANIFEST.MF new file mode 100644 index 00000000..d5f528c5 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/META-INF/MANIFEST.MF @@ -0,0 +1,31 @@ +Manifest-Version: 1.0 +Automatic-Module-Name: org.emoflon.gips.debugger.cplexlp +Bundle-ManifestVersion: 2 +Bundle-Name: Gips::Debugger::CplexLp +Bundle-Vendor: Real-Time Systems Lab - TU Darmstadt +Bundle-Version: 1.0.0.qualifier +Bundle-SymbolicName: org.emoflon.gips.debugger.cplexlp; singleton:=true +Bundle-ActivationPolicy: lazy +Require-Bundle: org.eclipse.xtext, + org.eclipse.xtext.xbase, + org.eclipse.equinox.common;bundle-version="3.16.0", + org.eclipse.emf.ecore, + org.eclipse.xtext.xbase.lib;bundle-version="2.14.0", + org.eclipse.xtext.util, + org.eclipse.emf.common, + org.antlr.runtime;bundle-version="[3.2.0,3.2.1)", + org.eclipse.core.resources +Bundle-RequiredExecutionEnvironment: JavaSE-21 +Export-Package: org.emoflon.gips.debugger, + org.emoflon.gips.debugger.cplexLp, + org.emoflon.gips.debugger.cplexLp.impl, + org.emoflon.gips.debugger.cplexLp.util, + org.emoflon.gips.debugger.generator, + org.emoflon.gips.debugger.parser.antlr, + org.emoflon.gips.debugger.parser.antlr.internal, + org.emoflon.gips.debugger.parser.antlr.lexer, + org.emoflon.gips.debugger.scoping, + org.emoflon.gips.debugger.serializer, + org.emoflon.gips.debugger.services, + org.emoflon.gips.debugger.validation +Import-Package: org.apache.log4j diff --git a/org.emoflon.gips.debugger.cplexlp/build.properties b/org.emoflon.gips.debugger.cplexlp/build.properties new file mode 100644 index 00000000..3e516cd4 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/build.properties @@ -0,0 +1,19 @@ +source.. = src/,\ + src-gen/,\ + xtend-gen/ +bin.includes = model/generated/,\ + .,\ + META-INF/,\ + plugin.xml +bin.excludes = **/*.mwe2,\ + **/*.xtend +additional.bundles = org.eclipse.xtext.xbase,\ + org.eclipse.xtext.common.types,\ + org.eclipse.xtext.xtext.generator,\ + org.eclipse.emf.codegen.ecore,\ + org.eclipse.emf.mwe.utils,\ + org.eclipse.emf.mwe2.launch,\ + org.eclipse.emf.mwe2.lib,\ + org.objectweb.asm,\ + org.apache.commons.logging,\ + org.apache.log4j diff --git a/org.emoflon.gips.debugger.cplexlp/model/generated/CplexLp.ecore b/org.emoflon.gips.debugger.cplexlp/model/generated/CplexLp.ecore new file mode 100644 index 00000000..7820e3b4 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/model/generated/CplexLp.ecore @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.cplexlp/model/generated/CplexLp.genmodel b/org.emoflon.gips.debugger.cplexlp/model/generated/CplexLp.genmodel new file mode 100644 index 00000000..4b744081 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/model/generated/CplexLp.genmodel @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.cplexlp/plugin.properties b/org.emoflon.gips.debugger.cplexlp/plugin.properties new file mode 100644 index 00000000..583b54ac --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/plugin.properties @@ -0,0 +1,4 @@ +# generated by Xtext 2.34.0 + +pluginName = org.emoflon.gips.debugger.cplexlp +providerName = My Company diff --git a/org.emoflon.gips.debugger.cplexlp/plugin.xml b/org.emoflon.gips.debugger.cplexlp/plugin.xml new file mode 100644 index 00000000..b6bd208f --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/plugin.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLp.xtext b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLp.xtext new file mode 100644 index 00000000..43fd4e51 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLp.xtext @@ -0,0 +1,170 @@ +grammar org.emoflon.gips.debugger.CplexLp /*with org.eclipse.xtext.common.Terminals*/ hidden(WS, ML_COMMENT, SL_COMMENT) +import "http://www.eclipse.org/emf/2002/Ecore" as ecore +generate cplexLp "http://www.emoflon.org/gips/debugger/CplexLp" + +Model: + objective = SectionObjective + constraint = SectionConstraint? + bound = SectionBound? + (integerValue = SectionIntegerValue? & binaryValue = SectionBinaryValue?) + 'End' +; + +SectionObjective: + goal = (MIN | MAX) + statement = ObjectiveExpression? +; + +MIN: 'minimize' | 'minimum' | 'min'; +MAX: 'maximize' | 'maximum' | 'max'; + +ObjectiveExpression: + (name=ValidID ':')? + expression = LinearExpression +; + +SectionConstraint: {SectionConstraint} + (('subject' 'to') | ('such' 'that') | 'st' | 's.t.' | 'st.') + (statements += ConstraintExpression )* +; + +ConstraintExpression: + (name=ValidID ':')? + left = LinearExpression + relation = RelationalOperator + right = LinearConstant + //EndOfLine +; + +SectionBound: {SectionBound} + ('bound' | 'bounds') + statements += BoundExpression* +; + +BoundExpression: + UpperAndLowerBoundExpression | LowerBoundExpression | UpperBoundExpression | EqualsToBoundExpression | FreeBoundExpression +; + +UpperAndLowerBoundExpression: + lowerBound = LinearConstant + '<=' + variable = VariableRef + '<=' + upperBound = LinearConstant +; + +LowerBoundExpression: + lowerBound = LinearConstant + '<=' + variable = VariableRef +; + +UpperBoundExpression: + variable = VariableRef + '<=' + upperBound = LinearConstant +; + +EqualsToBoundExpression: + variable = VariableRef + '=' + bound = LinearConstant +; + +FreeBoundExpression: + variable = VariableRef 'free' +; + +SectionIntegerValue: {SectionIntegerValue} + ('general' | 'generals' | 'gen') + variables += VariableDecleration* +; + +SectionBinaryValue: {SectionBinaryValue} + ('binary' | 'binaries' | 'bin') + variables += VariableDecleration* +; + +LinearExpression: + terms += FirstLinearTerm + (terms += LinearTerm)* +; + +LinearTerm: + operator=LinearOperators ( + coefficient=NumberLiteral variable=VariableRef | + coefficient=NumberLiteral | + variable=VariableRef + ) +; + +FirstLinearTerm returns LinearTerm: + operator=LinearOperators? ( + coefficient=NumberLiteral variable=VariableRef | + coefficient=NumberLiteral | + variable=VariableRef + ) +; + +LinearConstant: + NumberLiteral | InfinityLiteral +; + +Variable: VariableDecleration | VariableRef; + +VariableRef: // returns Variable: + ref=[VariableDecleration|ValidID] +; + +VariableDecleration: //returns Variable: + name = ValidID +; + +ValidID: ID; + +NumberLiteral: value = Number; +InfinityLiteral: value = Infinity; +//RelationLiteral: value = RelationOperator; + +Number : FLOAT | INTEGER; +terminal INTEGER returns ecore::EInt: '-'? DIGIT+; +terminal FLOAT returns ecore::EDouble: INTEGER '.' DIGIT+; + +enum LinearOperators: + PLUS = '+' | + MINUS = '-' +; + +enum Infinity: + POS_INFINITY = '+infinity' | + POS_INFINITY = '+inf' | + NEG_INFINITY = '-infinity' | + NEG_INFINITY = '-inf' +; + +enum RelationalOperator: + EqualTo = '=' | + GreaterThan = '>' | + GreaterThanOrEqual = '>=' | + GreaterThanOrEqual = '=>' | + LessThan = '<' | + LessThanOrEqual = '<=' | + LessThanOrEqual = '=<' +; + +//@Override +terminal ID returns ecore::EString: + ('a'..'z'|'A'..'Z'|'!'|'"'|'#'|'$'|'%'|'&'|'('|')'|'/'|','|';'|'?'|'@'|'_'|'`'|"'"|'{'|'}'|'|'|'~')('a'..'z'|'A'..'Z'|'0'..'9'|'!'|'"'|'#'|'$'|'%'|'&'|'('|')'|'/'|','|'.'|';'|'?'|'@'|'_'|'`'|"'"|'{'|'}'|'|'|'~'|'>'|'-')* +; + +// needed to support negative integers and floats +// org.eclipse.xtext.common.Terminals can't be included because of the INT Rule +terminal fragment DIGIT: '0'..'9'; + + //from org.eclipse.xtext.common.Terminals +terminal ML_COMMENT : '\\*' -> '*\\'; +terminal SL_COMMENT : '\\' !('\n'|'\r')* ('\r'? '\n')?; +terminal WS : (' '|'\t'|'\r'|'\n')+; +terminal ANY_OTHER: .; + +//terminal END_OF_LINE: ('\r'? '\n'); \ No newline at end of file diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLpRuntimeModule.java b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLpRuntimeModule.java new file mode 100644 index 00000000..fd240e9d --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLpRuntimeModule.java @@ -0,0 +1,26 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger; + +/** + * Use this class to register components to be used at runtime / without the + * Equinox extension registry. + */ +public class CplexLpRuntimeModule extends AbstractCplexLpRuntimeModule { + +// Class bindIStratumBreakpointSupport() { +// return StratumBreakpointSupport.class; +// } +// +// @Override +// public Class bindILinker() { +// return LazyLinker.class; +// } +// +// @Override +// public Class bindILinkingService() { +// return CplexLpLinker.class; +// } + +} diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLpStandaloneSetup.java b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLpStandaloneSetup.java new file mode 100644 index 00000000..46628910 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/CplexLpStandaloneSetup.java @@ -0,0 +1,15 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger; + + +/** + * Initialization support for running Xtext languages without Equinox extension registry. + */ +public class CplexLpStandaloneSetup extends CplexLpStandaloneSetupGenerated { + + public static void doSetup() { + new CplexLpStandaloneSetup().createInjectorAndDoEMFRegistration(); + } +} diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/GenerateCplexLp.mwe2 b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/GenerateCplexLp.mwe2 new file mode 100644 index 00000000..0f0f3a5e --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/GenerateCplexLp.mwe2 @@ -0,0 +1,59 @@ +module org.emoflon.gips.debugger.GenerateCplexLp + +import org.eclipse.xtext.xtext.generator.* +import org.eclipse.xtext.xtext.generator.model.project.* + +var rootPath = ".." + +Workflow { + + component = XtextGenerator { + configuration = { + project = StandardProjectConfig { + baseName = "org.emoflon.gips.debugger.cplexlp" + rootPath = rootPath + runtimeTest = { + enabled = false + } + eclipsePlugin = { + enabled = true + } + eclipsePluginTest = { + enabled = false + } + createEclipseMetaData = true + } + code = { + encoding = "UTF-8" + lineDelimiter = "\r\n" + fileHeader = "/*\n * generated by Xtext \${version}\n */" + preferXtendStubs = false + } + } + language = StandardLanguage { + name = "org.emoflon.gips.debugger.CplexLp" + fileExtensions = "lp" + + serializer = { + generateStub = false + } + validator = { + // composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator" + // Generates checks for @Deprecated grammar annotations, an IssueProvider and a corresponding PropertyPage + generateDeprecationValidation = true + } + generator = { + generateXtendStub = true + } + parserGenerator = { + options = { + ignoreCase = true //Case Insensitive Keywords + } + debugGrammar = true + } + junitSupport = { + junitVersion = "5" + } + } + } +} diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/generator/CplexLpGenerator.xtend b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/generator/CplexLpGenerator.xtend new file mode 100644 index 00000000..ee1c9a53 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/generator/CplexLpGenerator.xtend @@ -0,0 +1,124 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.generator + +import org.eclipse.emf.ecore.resource.Resource +import org.eclipse.xtext.generator.AbstractGenerator +import org.eclipse.xtext.generator.IFileSystemAccess2 +import org.eclipse.xtext.generator.IGeneratorContext +import org.eclipse.xtext.generator.trace.node.TracedAccessors +import com.google.inject.Inject +import org.emoflon.gips.debugger.cplexLp.CplexLpFactory +import org.emoflon.gips.debugger.cplexLp.Model +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.util.EcoreUtil +import org.eclipse.core.resources.ResourcesPlugin +import org.eclipse.core.resources.IWorkspace +import org.eclipse.core.resources.IProject +import java.io.FileNotFoundException + +/** + * Generates code from your model files on save. + * + * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation + */ +class CplexLpGenerator extends AbstractGenerator { + +// @TracedAccessors(CplexLpFactory) +// static class CplexLpTraceExtensions { +// } + +// @Inject +// extension CplexLpTraceExtensions + +//private void exportXMI(String absuloteTargetFolderPath) { +// // change MyLanguage with your language name +// Injector injector = new MyLanguageStandaloneSetup() +// .createInjectorAndDoEMFRegistration(); +// XtextResourceSet resourceSet = injector +// .getInstance(XtextResourceSet.class); +// +// // .ext ist the extension of the model file +// String inputURI = "file:///" + absuloteTargetFolderPath + "/MyFile.ext"; +// String outputURI = "file:///" + absuloteTargetFolderPath + "/MyFile.xmi"; +// URI uri = URI.createURI(inputURI); +// Resource xtextResource = resourceSet.getResource(uri, true); +// +// EcoreUtil.resolveAll(xtextResource); +// +// Resource xmiResource = resourceSet +// .createResource(URI.createURI(outputURI)); +// xmiResource.getContents().add(xtextResource.getContents().get(0)); +// try { +// xmiResource.save(null); +// } catch (IOException e) { +// e.printStackTrace(); +// } +//} + + + override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { +// Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()) +// +// val rs = new ResourceSetImpl; +// rs.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()) +// val output = rs.createResource(URI.createURI(resource.URI.trimFileExtension+".xmi")) +// +// val model = resource.contents.get(0) as Model; +// output.contents.add(model) +// EcoreUtil.resolveAll(output) +// +// val workspace = getWorkspace() +// val project = getProjectOfResource(workspace, output) +// if(project === null) +// throw new FileNotFoundException("Could not find xtext model file: "+ output.URI.path) +// +// output.save(null); + +// for (model : resource.allContents.filter(Model).toIterable) { +// val name = resource.URI.trimFileExtension.lastSegment +// fsa.generateTracedFile("./lpfiles/" + name + ".java", model, ''' +// package org.emoflon.gips.debugger.cplexLp; +// +// public class Lp_«name» { +// public static void main(String[] args) { +// System.out.println("«model.objective.goal»"); +// System.out.println("«model.objective.statement.name» : «model.objective.statement.expression»"); +// +// «FOR cs : model.constrain.statements» +// System.out.println("«cs.name»: «cs.left» «cs.relation» «cs.right»"); +// «ENDFOR» +// +// «FOR bs : model.bound.statements» +// System.out.println("«bs»"); +// «ENDFOR» +// } +// +// } +// ''') +// } + } + + def static IWorkspace getWorkspace() { + return ResourcesPlugin.getWorkspace() + } + + + def static IProject getProjectOfResource(IWorkspace workspace, Resource resource) { + if(resource.URI.segmentCount<2) + return null; + + for(project : workspace.root.projects) { + val projectName = resource.URI.segment(1) + if(project.name.equalsIgnoreCase(projectName)) { + return project; + } + } + + return null; + } +} + diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/linker/CplexLpLinker.java b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/linker/CplexLpLinker.java new file mode 100644 index 00000000..4bc0c409 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/linker/CplexLpLinker.java @@ -0,0 +1,42 @@ +package org.emoflon.gips.debugger.linker; + +import java.util.List; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.linking.ILinkingService; +import org.eclipse.xtext.linking.impl.DefaultLinkingService; +import org.eclipse.xtext.nodemodel.INode; + +public class CplexLpLinker extends DefaultLinkingService implements ILinkingService { + + public static String TMP_RESOURCE = "CplexLP Dummy Resource"; + + private Resource getTmpResource(EObject context) { + var uri = URI.createURI(TMP_RESOURCE); + var rs = context.eResource().getResourceSet(); + var resource = rs.getResource(uri, false); + if (resource == null) { + resource = rs.createResource(uri); + } + return resource; + } + + @Override + public List getLinkedObjects(EObject context, EReference ref, INode node) { + var result = super.getLinkedObjects(context, ref, node); +// if (result.isEmpty() && context instanceof Variable && ref == CplexLpPackage.Literals.VARIABLE_REF +// && ((Variable) context).getName() == null) { +// var baseVariable = CplexLpFactory.eINSTANCE.createVariableDecleration(); +// baseVariable.setName(node.getText().trim()); +// +// var tmpResource = getTmpResource(context); +// tmpResource.getContents().add(baseVariable); +// result = Collections.singletonList(baseVariable); +// } + return result; + } + +} diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/scoping/CplexLpScopeProvider.java b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/scoping/CplexLpScopeProvider.java new file mode 100644 index 00000000..5304f460 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/scoping/CplexLpScopeProvider.java @@ -0,0 +1,126 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.scoping; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.xtext.scoping.IScope; + +/** + * This class contains custom scoping description. + * + * See + * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#scoping + * on how and when to use it. + */ +public class CplexLpScopeProvider extends AbstractCplexLpScopeProvider { + + @Override + public IScope getScope(EObject context, EReference reference) { +// if (reference == CplexLpPackage.Literals.VARIABLE__REF) { +// +// } + return super.getScope(context, reference); + } + +// @override +// public getScope(EObject context, EReference reference) { +// if (reference === MyDslPackage.Literals.USE__REFERENCE) { +// val model = EcoreUtil2.getContainerOfType(context, Model) +// if (model !== null) { +// val result = newArrayList +// for (i : model.instances) { +// result.add( EObjectDescription.create( +// QualifiedName.create( i.variable.name ), i.variable )) +// for (v : i.type.variables) { +// result.add( EObjectDescription.create( +// QualifiedName.create( i.variable.name, v.variable.name ), +// v.variable )) +// } +// } +// println(result) +// return new SimpleScope(IScope.NULLSCOPE, result) +// } +// } +// super.getScope(context, reference) +// } + +// protected Map variableLookup = new HashMap<>(); +// +// IScope scope_Call_op(VariableIdentifier variable, EReference ref) { +// // return Scopes.scopeFor(call.getVar().getType().getOps()); +// +// var rootElement = EcoreUtil2.getRootContainer(variable); +// var possibleRefs = EcoreUtil2.getAllContentsOfType(rootElement, VariableIdentifier.class); +// +// return Scopes.scopeFor(possibleRefs); +// +//// var result = new LinkedList(); +//// result.addAll(possibleRefs); +//// +//// return new SimpleScope(IScope.NULLSCOPE, result); +// } + +// protected Map variableRefLookup = new HashMap<>(); + +// @Override +// public IScope getScope(EObject context, EReference reference) { +// System.out.println("Do some work?"); +// if (context instanceof VariableIdentifier) { +// var variable = (VariableIdentifier) context; +// System.out.println("Check for reference: " + variable.getName()); +// if (reference == CplexLpPackage.Literals.VARIABLE_IDENTIFIER__REF) { +// var ref = variableRefLookup.putIfAbsent(variable.getName(), variable); +// if (Objects.equal(ref, context)) +// return IScope.NULLSCOPE; +// +// return Scopes.scopeFor(List.of(ref)); +// } +//// +//// System.out.println("Check for reference: " + ((VariableIdentifier) context).getName()); +//// System.out.println("Ref type: " + (reference == CplexLpPackage.Literals.VARIABLE_IDENTIFIER__REF)); +//// System.out.println("Ref type: " + (reference == CplexLpPackage.Literals.VARIABLE_IDENTIFIER__NAME)); +// } +// return super.getScope(context, reference); +// } + +// @Override +// public IScope getScope(EObject context, EReference reference) { +// if(context instanceof Variable) { +// var rootElement = EcoreUtil2.getRootContainer(context); +// var possibleRefs = EcoreUtil2.getAllContentsOfType(rootElement, Variable.class); +// return Scopes.scopeFor(possibleRefs); +// } +// +// if(context instanceof Variable && reference == CplexLpPackage.Literals.VARIABLE__NAME) { +// Variable variable = (Variable)context; +// Variable refVariable = variableLookup.putIfAbsent(variable.getName(), variable); +// if(!Objects.equal(variable, refVariable)) +// return Scopes.scopeFor(List.of(refVariable)); +//// +//// if(refVariable == null) { +//// var rootElement = EcoreUtil2.getRootContainer(context); +//// var possibleRefs = EcoreUtil2.getAllContentsOfType(rootElement, Variable.class); +//// +//// for(var possibleRef : possibleRefs) { +//// if(!Objects.equal(possibleRef, variable)) { +//// if(possibleRef.getName().equals(variable.getName())){ +//// refVariable = possibleRef; +//// variableLookup.put(refVariable.getName(), refVariable); +//// break; +//// } +//// } +//// } +//// }else if(Objects.equal(refVariable, variable)) { +//// refVariable = null; +//// } +//// +//// if(refVariable != null) +//// return Scopes.scopeFor(List.of(refVariable)); +// } +// +// return super.getScope(context, reference); +// } + +} diff --git a/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/validation/CplexLpValidator.java b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/validation/CplexLpValidator.java new file mode 100644 index 00000000..d9432fe0 --- /dev/null +++ b/org.emoflon.gips.debugger.cplexlp/src/org/emoflon/gips/debugger/validation/CplexLpValidator.java @@ -0,0 +1,99 @@ +/* + * generated by Xtext 2.33.0 + */ +package org.emoflon.gips.debugger.validation; + +import org.eclipse.xtext.validation.Check; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +import org.emoflon.gips.debugger.cplexLp.CplexLpPackage; +import org.emoflon.gips.debugger.cplexLp.VariableDecleration; +import org.emoflon.gips.debugger.services.CplexLpGrammarAccess; + +import com.google.inject.Inject; + +/** + * This class contains custom validation rules. + * + * See + * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation + */ +public class CplexLpValidator extends AbstractCplexLpValidator { + + @Inject + private CplexLpGrammarAccess grammarAccess; + + @Check + public void checkGreetingStartsWithCapital(VariableDecleration variable) { + if (variable.getName() == null) { + return; + } + + var firstLetter = variable.getName().charAt(0); + if (!(firstLetter == 'e' || firstLetter == 'E')) { + return; + } + + warning("Name should not start with the letter 'e'", CplexLpPackage.Literals.VARIABLE_DECLERATION__NAME, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, "InvalidTypeName", variable.getName()); + +// var node = NodeModelUtils.findActualNodeFor(greeting); + +// if (!Character.isUpperCase(greeting.name.charAt(0))) { +// val node = NodeModelUtils.findActualNodeFor(greeting) +// +// for (n : node.asTreeIterable) { +// val ge = n.grammarElement +// if (ge instanceof Keyword && ge == greetingAccess.helloKeyword_0) { +// messageAcceptor.acceptWarning( +// 'Name should start with a capital', +// greeting, +// n.offset, +// n.length, +// INVALID_NAME +// ) +// } +// } +// +// } + } + +// @Check +// public void checkVariableIsBound(Variable variable) { +// var model = (Model) EcoreUtil2.getRootContainer(variable); +// var boundSection = model.getBound(); +// if(boundSection != null) { +// +// } +// warning("Variable is not bound", CplexLpPackage.Literals.VARIABLE__NAME, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, "NotBound", variable.getName()); +// } + +// @Check +// public void checkVariableIsCorrectlyBound(BoundExpression variable) { +// var model = (Model) EcoreUtil2.getRootContainer(variable); +// var boundSection = model.getBound(); +// if(boundSection != null) { +// variable.g +// } +// warning("It's bound!", CplexLpPackage.Literals.BOUND_EXPRESSION__VARIABLE, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, "NotBound"); +// } + +// @Check +// public void checkTypeNameStartsWithCapital(ObjectiveExpression equation) { +// if(equation.getName() != null) { +// if(equation.getName().charAt(0) == 'e') { +// warning("Name should not start with the letter 'e'", CplexLpPackage.Literals.OBJECTIVE_EXPRESSION__NAME, +// ValidationMessageAcceptor.INSIGNIFICANT_INDEX, "InvalidTypeName", equation.getName()); +// } +// } +// } +// +// public void checkTypeNameStartsWithCapital(ConstrainExpression equation) { +// if(equation.getName() != null) { +// if(equation.getName().charAt(0) == 'e') { +// warning("Name should not start with the letter 'e'", CplexLpPackage.Literals.CONSTRAIN_EXPRESSION__NAME, +// ValidationMessageAcceptor.INSIGNIFICANT_INDEX, "InvalidTypeName", equation.getName()); +// } +// } +// } + +} diff --git a/org.emoflon.gips.debugger.trace/.classpath b/org.emoflon.gips.debugger.trace/.classpath new file mode 100644 index 00000000..c3f9cf33 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.emoflon.gips.debugger.trace/.gitignore b/org.emoflon.gips.debugger.trace/.gitignore new file mode 100644 index 00000000..95c365bf --- /dev/null +++ b/org.emoflon.gips.debugger.trace/.gitignore @@ -0,0 +1,63 @@ +.metadata +bin/ +test-bin/ +src-gen/ +xtend-gen/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project \ No newline at end of file diff --git a/org.emoflon.gips.debugger.trace/.project b/org.emoflon.gips.debugger.trace/.project new file mode 100644 index 00000000..88afccb2 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/.project @@ -0,0 +1,29 @@ + + + org.emoflon.gips.debugger.trace + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.sirius.nature.modelingproject + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/org.emoflon.gips.debugger.trace/META-INF/MANIFEST.MF b/org.emoflon.gips.debugger.trace/META-INF/MANIFEST.MF new file mode 100644 index 00000000..f11966c4 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.emoflon.gips.debugger.trace;singleton:=true +Automatic-Module-Name: org.emoflon.gips.debugger.trace +Bundle-Version: 1.0.0.qualifier +Bundle-ClassPath: . +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Bundle-RequiredExecutionEnvironment: JavaSE-21 +Export-Package: org.emoflon.gips.debugger.trace, + org.emoflon.gips.debugger.trace.impl, + org.emoflon.gips.debugger.trace.resolver, + org.emoflon.gips.debugger.trace.util +Require-Bundle: org.eclipse.core.runtime;resolution:=optional, + org.eclipse.emf.ecore;visibility:=reexport, + org.eclipse.emf.ecore.xmi +Bundle-ActivationPolicy: lazy diff --git a/org.emoflon.gips.debugger.trace/build.properties b/org.emoflon.gips.debugger.trace/build.properties new file mode 100644 index 00000000..697ca964 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/build.properties @@ -0,0 +1,10 @@ +# + +bin.includes = .,\ + model/,\ + META-INF/,\ + plugin.xml,\ + plugin.properties +jars.compile.order = . +source.. = src-gen/ +output.. = bin/ diff --git a/org.emoflon.gips.debugger.trace/model/TraceModel.aird b/org.emoflon.gips.debugger.trace/model/TraceModel.aird new file mode 100644 index 00000000..f29ca057 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/model/TraceModel.aird @@ -0,0 +1,363 @@ + + + + TraceModel.ecore + TraceModel.genmodel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bold + + + + + + + + + + + + + + + + bold + + + + bold + + + + + + + + + + + + + + + + + + + + + + + + + + bold + + + + + + + + + + + + + + + + bold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bold + + + bold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.trace/model/TraceModel.ecore b/org.emoflon.gips.debugger.trace/model/TraceModel.ecore new file mode 100644 index 00000000..1726baf2 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/model/TraceModel.ecore @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.trace/model/TraceModel.genmodel b/org.emoflon.gips.debugger.trace/model/TraceModel.genmodel new file mode 100644 index 00000000..515481c6 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/model/TraceModel.genmodel @@ -0,0 +1,41 @@ + + + TraceModel.ecore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.trace/plugin.properties b/org.emoflon.gips.debugger.trace/plugin.properties new file mode 100644 index 00000000..bed72332 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/plugin.properties @@ -0,0 +1,4 @@ +# + +pluginName = Gips::Debugger::Trace +providerName = Real-Time Systems Lab - TU Darmstadt diff --git a/org.emoflon.gips.debugger.trace/plugin.xml b/org.emoflon.gips.debugger.trace/plugin.xml new file mode 100644 index 00000000..a0b7904c --- /dev/null +++ b/org.emoflon.gips.debugger.trace/plugin.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/EcoreReader.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/EcoreReader.java new file mode 100644 index 00000000..75fb2556 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/EcoreReader.java @@ -0,0 +1,60 @@ +package org.emoflon.gips.debugger.trace; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.URIConverter; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; + +/** + * Helper class for loading XMI files from disk + */ +public final class EcoreReader { + + private final URI modelURI; + private ResourceSetImpl resourceSet; + + public EcoreReader(URI modelURI) { + this.modelURI = Objects.requireNonNull(modelURI); + initializeResourceSet(); + } + + public EcoreReader(IPath path) { + if (path.isAbsolute()) { + this.modelURI = URI.createFileURI(path.toString()); + } else { + this.modelURI = URI.createPlatformResourceURI(path.toString(), true); + } + initializeResourceSet(); + } + + private void initializeResourceSet() { + this.resourceSet = new ResourceSetImpl(); +// Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()); + resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()); + resourceSet.getPackageRegistry().put(TraceModelPackage.eINSTANCE.getNsURI(), TraceModelPackage.eINSTANCE); + + } + + public boolean doesFileExist() { + return resourceSet.getURIConverter().exists(modelURI, null); + } + + public long getFileTimeStamp() { + var requestedAttributes = Set.of(URIConverter.ATTRIBUTE_TIME_STAMP); + var options = Collections.singletonMap(URIConverter.OPTION_REQUESTED_ATTRIBUTES, requestedAttributes); + var attributes = resourceSet.getURIConverter().getAttributes(modelURI, options); + var timeStamp = (Long) attributes.get(URIConverter.ATTRIBUTE_TIME_STAMP); + return timeStamp == null ? -1 : timeStamp; + } + + public Root loadModel() { + var resource = resourceSet.getResource(modelURI, true); + return (Root) resource.getContents().get(0); + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/EcoreWriter.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/EcoreWriter.java new file mode 100644 index 00000000..dd6bb245 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/EcoreWriter.java @@ -0,0 +1,45 @@ +package org.emoflon.gips.debugger.trace; + +import java.io.IOException; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.XMIResource; +import org.eclipse.emf.ecore.xmi.XMLResource; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; + +/** + * Helper class for saving {@link TraceModelPackage Ecore trace models} to disk + * as XMI files + */ +public final class EcoreWriter { + private EcoreWriter() { + + } + + public static void saveModel(Root root, URI saveLocation) { + ResourceSet resourceSet = new ResourceSetImpl(); + resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl()); + resourceSet.getPackageRegistry().put(TraceModelPackage.eINSTANCE.getNsURI(), TraceModelPackage.eINSTANCE); + + Resource output = resourceSet.createResource(saveLocation); + output.getContents().add(root); + + Map saveOptions = ((XMIResource) output).getDefaultSaveOptions(); + saveOptions.put(XMLResource.OPTION_ENCODING, "UTF-8"); + saveOptions.put(XMIResource.OPTION_USE_XMI_TYPE, Boolean.TRUE); + saveOptions.put(XMLResource.OPTION_SAVE_TYPE_INFORMATION, Boolean.TRUE); + saveOptions.put(XMLResource.OPTION_SCHEMA_LOCATION_IMPLEMENTATION, Boolean.TRUE); + + try { + output.save(saveOptions); + } catch (final IOException e) { + e.printStackTrace(); + } + output.unload(); + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/PathFinder.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/PathFinder.java new file mode 100644 index 00000000..652dc939 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/PathFinder.java @@ -0,0 +1,203 @@ +package org.emoflon.gips.debugger.trace; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A helper class for finding possible paths between two models within the same + * {@link TraceGraph graph}. + */ +public final class PathFinder { + private PathFinder() { + } + + /** + * A sequence of transformations leading from a source model to a destination + * model. + */ + public static record TracePath(List path, OverallDirection direction) { + } + + public static record TracePathLink(String srcModelId, String dstModelId, Direction direction) { + } + + /** + * This describes the overall direction of all transformations in a path. + * Possible values are: + *
    + *
  • {@link OverallDirection#Forward} + *
  • {@link OverallDirection#Backward} + *
  • {@link OverallDirection#Mixed} + *
+ */ + public static enum OverallDirection { + /** + * The path consists only of forward transformations. + */ + Forward, + + /** + * The path consists only of backward transformations. + */ + Backward, + + /** + * The path contains forward and backward transformations. + */ + Mixed + } + + /** + * This describes the direction of a single transformation. Possible values are: + *
    + *
  • {@link Direction#Forward} + *
  • {@link Direction#Backward} + *
+ */ + public static enum Direction { + /** + * This is a forward transformation from src to dst. + */ + Forward, + /** + * This is a backward transformation from dst to src. + */ + Backward + } + + public static enum SearchDirection { + /** + * This option considers forward transformations only. + */ + OnlyForward, + /** + * This option considers backward transformations only. + */ + OnlyBackward, + + AllDirections + } + + private static final class Helper { + + private final TraceGraph graph; + private final String srcModelId; + private final String dstModelId; + private final SearchDirection searchDirection; + private final LinkedList currentPath = new LinkedList<>(); + private final Set alreadySeen = new HashSet<>(); + private final Set> allPaths = new HashSet<>(); + + public Helper(TraceGraph graph, String srcModelId, String dstModelId, SearchDirection searchDirection) { + this.graph = Objects.requireNonNull(graph, "graph"); + this.srcModelId = Objects.requireNonNull(srcModelId, "srcModelId"); + this.dstModelId = Objects.requireNonNull(dstModelId, "dstModelId"); + this.searchDirection = searchDirection; + + if (this.srcModelId.equals(dstModelId)) { + throw new IllegalArgumentException("Unable to compute circular paths"); + } + } + + public Set findPaths() { + alreadySeen.add(srcModelId); + searchNext(srcModelId); + + Set results = new HashSet<>(); + for (List path : allPaths) { + switch (searchDirection) { + case OnlyForward: + results.add(new TracePath(path, OverallDirection.Forward)); + break; + case OnlyBackward: + results.add(new TracePath(path, OverallDirection.Backward)); + break; + default: + var direction = path.get(0).direction; + var overallDirection = direction == Direction.Forward ? OverallDirection.Forward + : OverallDirection.Backward; + + for (var pathLink : path) { + if (direction != pathLink.direction) { + overallDirection = OverallDirection.Mixed; + break; + } + } + + results.add(new TracePath(path, overallDirection)); + } + } + return results; + } + + private void searchNext(String currentModelId) { + switch (searchDirection) { + case OnlyForward: + searchNext(currentModelId, Direction.Forward); + break; + case OnlyBackward: + searchNext(currentModelId, Direction.Backward); + break; + case AllDirections: + searchNext(currentModelId, Direction.Forward); + searchNext(currentModelId, Direction.Backward); + break; + } + } + + private void searchNext(String currentModelId, Direction direction) { + Set nextModelIds = switch (direction) { + case Forward -> graph.getTargetModelIds(currentModelId); + case Backward -> graph.getSourceModelIds(currentModelId); + default -> throw new IllegalArgumentException("Unexpected value: " + direction); + }; + + for (var nextModelId : nextModelIds) { + if (alreadySeen.contains(nextModelId)) { + continue; // circles not allowed + } + + alreadySeen.add(nextModelId); + switch (direction) { + case Forward: + currentPath.add(new TracePathLink(currentModelId, nextModelId, direction)); + break; + case Backward: + currentPath.add(new TracePathLink(nextModelId, currentModelId, direction)); + break; + } + + if (nextModelId.equals(dstModelId)) { + allPaths.add(new ArrayList<>(currentPath)); + } else { + searchNext(nextModelId); + } + alreadySeen.remove(nextModelId); + currentPath.removeLast(); + } + } + } + + /** + * This method returns all paths from the source model to the destination model. + * For {@link SearchDirection#OnlyForward}, each path will only consist of + * forward transformations only. Otherwise, a path can contain both forward and + * backward transformations. + * + * @param graph + * @param srcModelId all paths start here + * @param dstModelId all paths end here + * @param searchDirection search direction + * @return + */ + public static Set computePaths(TraceGraph graph, String srcModelId, String dstModelId, + SearchDirection searchDirection) { + final var helper = new Helper(graph, srcModelId, dstModelId, searchDirection); + return helper.findPaths(); + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceChain.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceChain.java new file mode 100644 index 00000000..d95c8d9b --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceChain.java @@ -0,0 +1,141 @@ +package org.emoflon.gips.debugger.trace; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.emoflon.gips.debugger.trace.PathFinder.Direction; +import org.emoflon.gips.debugger.trace.PathFinder.TracePath; + +/** + * A trace chain is a set of paths which lead from a source to a destination + * model. + */ +public class TraceChain { + + private final WeakReference graph; + private final String srcModelId; + private final String dstModelId; + private final Set paths; + + public TraceChain(final TraceGraph graph, final String srcModelId, final String dstModelId, + final Set allPaths) { + + this.graph = new WeakReference<>(Objects.requireNonNull(graph, "graph")); + this.srcModelId = Objects.requireNonNull(srcModelId, "srcModelId"); + this.dstModelId = Objects.requireNonNull(dstModelId, "dstModelId"); + this.paths = Objects.requireNonNull(allPaths, "allPaths"); + + if (this.srcModelId.equals(dstModelId)) { + if (!allPaths.isEmpty()) { + throw new IllegalArgumentException("Chain links model to itself. Path must be empty"); + } + } + + for (var pathData : paths) { + var firstSegment = pathData.path().get(0); + var firstModelId = firstSegment.direction() == Direction.Forward ? firstSegment.srcModelId() + : firstSegment.dstModelId(); + if (!this.srcModelId.equals(firstModelId)) { + throw new IllegalArgumentException("First element of a path must be the source model"); + } + + var lastSegment = pathData.path().get(pathData.path().size() - 1); + var lastModelId = lastSegment.direction() == Direction.Forward ? lastSegment.dstModelId() + : lastSegment.srcModelId(); + if (!this.dstModelId.equals(lastModelId)) { + throw new IllegalArgumentException("Last element of a path must be the destination model"); + } + } + } + + private TraceGraph getGraph() { + var graph = this.graph.get(); + if (graph == null) { + throw new NullPointerException("TraceGraph disposed"); + } + return graph; + } + + public Set resolveElementFromStartToEnd(String elementId) { + return resolveElementFromStartToEnd(Collections.singleton(elementId)); + } + + public Set resolveElementFromStartToEnd(final Collection elementIds) { + return resolveElementsInTransformationDirection(true, elementIds); + } + + public Set resolveElementFromEndToStart(String elementId) { + return resolveElementFromEndToStart(Collections.singleton(elementId)); + } + + public Set resolveElementFromEndToStart(Collection elementIds) { + return resolveElementsInTransformationDirection(false, elementIds); + } + + private Set resolveElementsInTransformationDirection(boolean forward, Collection elementIds) { + if (elementIds.isEmpty()) { + return Collections.emptySet(); + } + + Set resolvedNodes = new HashSet<>(); + if (paths.isEmpty()) { + resolvedNodes.addAll(elementIds); + } + + TraceGraph graph = getGraph(); + + Collection currentNodes = elementIds; + for (var pathData : paths) { + if (forward) { + for (var path : pathData.path()) { + if (currentNodes.isEmpty()) { + break; + } + + final var link = graph.getLink(path.srcModelId(), path.dstModelId()); + if (path.direction() == Direction.Forward) { + currentNodes = link.resolveFromSrcToDst(currentNodes); + } else { + currentNodes = link.resolveFromDstToSrc(currentNodes); + } + } + } else { + var iterator = pathData.path().listIterator(pathData.path().size()); + while (iterator.hasPrevious()) { + if (currentNodes.isEmpty()) { + break; + } + + var previous = iterator.previous(); + final var link = graph.getLink(previous.srcModelId(), previous.dstModelId()); + if (previous.direction() == Direction.Forward) { + currentNodes = link.resolveFromDstToSrc(currentNodes); + } else { + currentNodes = link.resolveFromSrcToDst(currentNodes); + } + } + } + + resolvedNodes.addAll(currentNodes); + } + + return resolvedNodes; + } + + public boolean isResolved() { + return srcModelId.equals(dstModelId) || !paths.isEmpty(); + } + + public TraceModelReference getStartModel() { + return getGraph().getModelReference(srcModelId, false); + } + + public TraceModelReference getEndModel() { + return getGraph().getModelReference(dstModelId, false); + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceGraph.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceGraph.java new file mode 100644 index 00000000..b0c5d6e0 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceGraph.java @@ -0,0 +1,206 @@ +package org.emoflon.gips.debugger.trace; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.emoflon.gips.debugger.trace.PathFinder.SearchDirection; + +/** + * This class stores traces in a graph-based structure. Each model is + * represented by a unique id and a model transformation is stored as a + * source/target-relation between two ids. A source is the starting point of a + * transformation and the target its result. + * + */ +public class TraceGraph implements Serializable { + + private static final long serialVersionUID = -1428442558464158336L; + private transient Object lock = new Object(); + private final Map> links = new HashMap<>(); + private final Map> reverseLinks = new HashMap<>(); + private final Map storedReferences = new HashMap<>(); + +// private TraceGraph(TraceGraph traceGraph) { +// links.putAll(traceGraph.links); +// reverseLinks.putAll(traceGraph.reverseLinks); +// storedReferences.putAll(traceGraph.storedReferences); +// } +// +// /** +// * Part of {@link Serializable} +// * +// * @return +// */ +// private Object readResolve() { +// return new TraceGraph(this); +// } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.lock = new Object(); + } + + public TraceModelReference getModelReference(final String modelId, final boolean createOnDemand) { + final var ref = storedReferences.get(modelId); + if ((ref == null) && createOnDemand) { + return getOrCreateModelReference(modelId); + } + return ref; + } + + private TraceModelReference getOrCreateModelReference(final String modelId) { + Objects.requireNonNull(modelId, "modelId"); + synchronized (lock) { + var ref = storedReferences.get(modelId); + if (ref == null) { + ref = new TraceModelReference(modelId); + + links.put(modelId, new HashMap<>()); + reverseLinks.put(modelId, new HashSet<>()); + storedReferences.put(modelId, ref); + } + return ref; + } + } + + public boolean removeModelReference(final String modelId) { + synchronized (lock) { + final var ref = storedReferences.remove(modelId); + if (ref == null) { + return false; + } + + final var model2Target = links.remove(modelId); + for (final var targetId : model2Target.keySet()) { + final var targetCreatedBy = reverseLinks.get(targetId); + targetCreatedBy.remove(modelId); + } + + final var src2Model = reverseLinks.remove(modelId); + for (final var srcId : src2Model) { + final var modelCreatedBy = links.get(srcId); + modelCreatedBy.remove(modelId); + } + + return true; + } + } + + public void clear() { + synchronized (lock) { + links.clear(); + reverseLinks.clear(); + storedReferences.clear(); + } + } + + public boolean hasModelReference(final String modelId) { + return storedReferences.containsKey(modelId); + } + + public Set getAllModelReferenceIds() { + return Collections.unmodifiableSet(storedReferences.keySet()); + } + + public Collection getAllModelReferences() { + return Collections.unmodifiableCollection(storedReferences.values()); + } + + public boolean removeTraceLink(String srcModelId, String dstModelId) { + synchronized (lock) { + if (!hasModelReference(srcModelId) || !hasModelReference(dstModelId)) + return false; + + final var srcRef = getOrCreateModelReference(srcModelId); + final var dstRef = getOrCreateModelReference(dstModelId); + final var linksByDst = links.get(srcRef.getModelId()); + final var oldLink = linksByDst.remove(dstRef.getModelId()); + reverseLinks.get(dstRef.getModelId()).remove(srcRef.getModelId()); + + return oldLink != null; + } + } + + public void addOrReplaceTraceLink(TraceModelLink link) { + Objects.requireNonNull(link, "link"); + synchronized (lock) { + final var srcRef = getOrCreateModelReference(link.srcModelId); + final var dstRef = getOrCreateModelReference(link.dstModelId); + final var linksByDst = links.get(srcRef.getModelId()); + linksByDst.put(dstRef.getModelId(), link); + reverseLinks.get(dstRef.getModelId()).add(srcRef.getModelId()); + } + } + + /** + * + * + * Returns a set of target model ids. These models are directly (or partially) + * derived from the given model. + * + * @param modelId source model id + * @return a set of, non-null, model ids. The set can be empty. + */ + public Set getTargetModelIds(final String modelId) { + final var targets = links.get(modelId); + if (targets.isEmpty()) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(targets.keySet()); + } + + /** + * Returns a set of source model ids. These models were directly involved in the + * creation of the given model. + * + * @param modelId target model id + * @return a set of, non-null, model ids. The set can be empty. + */ + public Set getSourceModelIds(final String modelId) { + final var sources = reverseLinks.get(modelId); + if (sources.isEmpty()) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(sources); + } + + public TraceModelLink getLink(final String srcModelId, final String dstModelId) { + final var linksByDst = links.get(srcModelId); + final var link = linksByDst.get(dstModelId); + return link; + } + + public TraceChain buildTraceChain(final String srcModelId, final String dstModelId, SearchDirection directions) { + Objects.requireNonNull(srcModelId); + Objects.requireNonNull(dstModelId); + + synchronized (lock) { + checkModelReference(srcModelId); + checkModelReference(dstModelId); + + if (srcModelId.equals(dstModelId)) { + return new TraceChain(this, srcModelId, dstModelId, Collections.emptySet()); + } + + var allPaths = PathFinder.computePaths(this, srcModelId, dstModelId, directions); + return new TraceChain(this, srcModelId, dstModelId, allPaths); + } + } + + private TraceModelReference checkModelReference(final String modelId) { + final var ref = storedReferences.get(modelId); + if (ref == null) { + throw new IllegalArgumentException("Model '" + modelId + "' not found."); + } + return ref; + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceMap.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceMap.java new file mode 100644 index 00000000..a4fe0439 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceMap.java @@ -0,0 +1,241 @@ +package org.emoflon.gips.debugger.trace; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.emoflon.gips.debugger.trace.resolver.ResolveElement2Id; + +/** + * This class is used to record/map a transformation between two models. Its + * implementation is based on {@link HashMap}s and {@link HashSet}s. + * + *

+ * This implementation is not synchronized + * + * @param the (general) type of source elements + * @param the (general) type of target elements + */ +public class TraceMap implements Serializable { + + private static final long serialVersionUID = 7587509919729026599L; + + /** + * This method can be used to reduce two {@link TraceMap} into a new one. The + * new {@link TraceMap} consist only of source elements from {@code map1} and + * target elements from {@code map2}. + * + *

+ * A source element of {@code map1} is only included if at least one of its + * associated target elements is a source element of {@code map2} with a + * non-null target element. + * + * @param type of source elements of {@code map1} + * @param type of intermediate elements (target of {@code map1} and source + * of {@code map2}) + * @param type of target elements of {@code map2} + * @param map1 start + * @param map2 end + * @return a new {@link TraceMap} which directly maps source elements from + * {@code map1} to target elements from {@code map2} + */ + public static TraceMap condense(final TraceMap map1, final TraceMap map2) { + + final var compressedMapping = new TraceMap(); + final var exitPoints = new HashSet(); + + for (final var entryPoint : map1.getAllSources()) { + exitPoints.clear(); + + final var intermediates = map1.getTargets(entryPoint); + for (final var intermediate : intermediates) { + final var result = map2.getTargets(intermediate); + exitPoints.addAll(result); + } + + compressedMapping.mapOneToMany(entryPoint, exitPoints); + } + + return compressedMapping; + } + + /** + * This method merges two {@link TraceMap}s into a new one. + * + * @param + * @param + * @param map1 + * @param map2 + * @return a new {@link TraceMap} + */ + public static TraceMap merge(TraceMap map1, TraceMap map2) { + final var merged = new TraceMap(); + final var exitPoints = new HashSet(); + + for (var entryPoint : map1.getAllSources()) { + exitPoints.clear(); + + var exitPointsMap1 = map1.getTargets(entryPoint); + exitPoints.addAll(exitPointsMap1); + + var exitPointsMap2 = map2.getTargets(entryPoint); + exitPoints.addAll(exitPointsMap2); + + merged.mapOneToMany(entryPoint, exitPoints); + } + + for (var entryPoint : map2.getAllSources()) { + if (map1.hasSource(entryPoint)) { + continue; + } + + var exitPoints2 = map2.getTargets(entryPoint); + merged.mapOneToMany(entryPoint, exitPoints2); + } + + return merged; + } + + public static TraceMap normalize(final TraceMap map, + final ResolveElement2Id srcResolver, final ResolveElement2Id dstResolver) { + + final var newMap = new TraceMap(); + + for (final var source : map.getAllSources()) { + final String srcId = srcResolver.resolve(source); + if (srcId == null) { + throw new NullPointerException(); // TODO + } + + for (final var target : map.getTargets(source)) { + final String dstId = dstResolver.resolve(target); + if (dstId == null) { + throw new NullPointerException(); // TODO + } + newMap.map(srcId, dstId); + } + } + + return newMap; + } + + private final Map> forward = new HashMap<>(); + private final Map> backward = new HashMap<>(); + private S sourceDefault; + + public void setDefaultSource(final S source) { + this.sourceDefault = source; + } + + public void clearDefaultSource() { + this.sourceDefault = null; + } + + /** + * Maps a single element {@code source} to a single element {@code target}. + * + * @param source start element of a transformation + * @param target end element of a transformation + */ + public void map(final S source, final T target) { + forward.computeIfAbsent(source, key -> new HashSet()).add(target); + backward.computeIfAbsent(target, key -> new HashSet()).add(source); + } + + /** + * Maps a single element {@code source} to one or many elements {@code targets}. + * + * @param source + * @param targets + */ + public void mapOneToMany(final S source, final Collection targets) { + for (var target : targets) { + map(source, target); + } + } + + public void mapManyToOne(final Collection sources, final T target) { + for (var source : sources) { + map(source, target); + } + } + + public void mapManyToMany(final Collection sources, final Collection targets) { + for (var source : sources) { + for (var target : targets) { + map(source, target); + } + } + } + + public void mapWithDefaultSource(T target) { + if (this.sourceDefault == null) { + throw new IllegalStateException("Default source not set"); + } + map(this.sourceDefault, target); + } + + public void removeMapping(S source, T target) { + var forwardMapping = forward.get(source); + if (forwardMapping != null) { + forwardMapping.remove(target); + } + + var backwardMapping = backward.get(target); + if (backwardMapping != null) { + backwardMapping.remove(source); + } + } + + public Set getSources(final T target) { + final Set sources = backward.get(target); + if (sources == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(sources); + } + + public Set getTargets(final S source) { + final Set targets = forward.get(source); + if (targets == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(targets); + } + + public boolean hasSource(final S source) { + return getTargets(source).isEmpty(); + } + + public boolean hasTarget(final T target) { + return getSources(target).isEmpty(); + } + + public Set getAllSources() { + return Collections.unmodifiableSet(forward.keySet()); + } + + public Set getAllTargets() { + return Collections.unmodifiableSet(backward.keySet()); + } + + public void clear() { + forward.clear(); + backward.clear(); + sourceDefault = null; + } + + @Override + public TraceMap clone() { + final var copy = new TraceMap(); + for (var key : forward.keySet()) { + copy.mapOneToMany(key, forward.get(key)); + } + return copy; + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceModelLink.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceModelLink.java new file mode 100644 index 00000000..c4d409bc --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceModelLink.java @@ -0,0 +1,86 @@ +package org.emoflon.gips.debugger.trace; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * + */ +public class TraceModelLink implements Serializable { + + private static final long serialVersionUID = 4324514417881650135L; + public final String srcModelId; + public final String dstModelId; + public final TraceMap mappings; + + public TraceModelLink(String srcModelId, String dstModelId, TraceMap mappings) { + this.srcModelId = Objects.requireNonNull(srcModelId, "srcModelId"); + this.dstModelId = Objects.requireNonNull(dstModelId, "dstModelId"); + this.mappings = Objects.requireNonNull(mappings, "mappings"); + } + + public String getSourceModel() { + return srcModelId; + } + + public String getTargetModel() { + return dstModelId; + } + + public Set getSourceNodeIds() { + return mappings.getAllSources(); + } + + public Set getTargetNodeIds() { + return mappings.getAllTargets(); + } + + public Set resolveFromSrcToDst(String nodeId) { + return resolveFromSrcToDst(Collections.singleton(nodeId)); + } + + public Set resolveFromDstToSrc(String nodeId) { + return resolveFromDstToSrc(Collections.singleton(nodeId)); + } + + public Set resolveFromSrcToDst(Collection nodeIds) { + if (nodeIds.isEmpty()) { + return Collections.emptySet(); + } + + if (nodeIds.size() == 1) { + var nodeId = nodeIds.iterator().next(); + return mappings.getTargets(nodeId); + } else { + var createdNodes = new HashSet(); + for (var nodeId : nodeIds) { + var creates = mappings.getTargets(nodeId); + createdNodes.addAll(creates); + } + return createdNodes; + } + } + + public Set resolveFromDstToSrc(Collection nodeIds) { + if (nodeIds.isEmpty()) { + return Collections.emptySet(); + } + + if (nodeIds.size() == 1) { + var nodeId = nodeIds.iterator().next(); + return mappings.getSources(nodeId); + } else { + var createdBy = new HashSet(); + for (var nodeId : nodeIds) { + var tmp = mappings.getSources(nodeId); + createdBy.addAll(tmp); + } + return createdBy; + } + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceModelReference.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceModelReference.java new file mode 100644 index 00000000..dfe4e388 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TraceModelReference.java @@ -0,0 +1,29 @@ +package org.emoflon.gips.debugger.trace; + +import java.io.Serializable; +import java.util.Objects; + +public class TraceModelReference implements Serializable { + + private static final long serialVersionUID = 6457272488769375850L; + private final String modelId; + private String uri; + + public TraceModelReference(final String modelId) { + super(); + this.modelId = Objects.requireNonNull(modelId); + } + + public String getModelId() { + return modelId; + } + + public String getModelURI() { + return uri; + } + + public void setModelURI(String uri) { + this.uri = uri; + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TransformEcore2Graph.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TransformEcore2Graph.java new file mode 100644 index 00000000..960bb430 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TransformEcore2Graph.java @@ -0,0 +1,57 @@ +package org.emoflon.gips.debugger.trace; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Helper class for converting {@link TraceModelPackage Ecore trace models} to + * {@link TraceGraph TraceGraphs} + * + * @see TransformGraph2Ecore + */ +public final class TransformEcore2Graph { + private TransformEcore2Graph() { + + } + + public static TraceGraph buildGraphFromModel(Root root) { + var graph = new TraceGraph(); + return addModelToGraph(root, graph); + } + + public static TraceGraph addModelToGraph(Root root, TraceGraph graph) { + for (var srcRef : root.getModels()) { + var links = createTraceLinks(srcRef); + for (var link : links) { + graph.addOrReplaceTraceLink(link); + } + } + return graph; + } + + public static Collection createTraceLinks(ModelReference ref) { + var list = new ArrayList(ref.getTransformsTo().size()); + for (var modelTransformation : ref.getTransformsTo()) { + var link = createTraceLink(modelTransformation); + list.add(link); + } + return list; + } + + public static TraceModelLink createTraceLink(ModelTransformation transformation) { + var nodeMapping = createTraceMap(transformation); + return new TraceModelLink(transformation.getSource().getModelId(), transformation.getTarget().getModelId(), + nodeMapping); + } + + public static TraceMap createTraceMap(ModelTransformation transformation) { + var nodeMapping = new TraceMap(); + for (var nodeTransformation : transformation.getTransformations()) { + var srcNodeId = nodeTransformation.getSource().getElementId(); + var dstNodeId = nodeTransformation.getTarget().getElementId(); + nodeMapping.map(srcNodeId, dstNodeId); + } + return nodeMapping; + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TransformGraph2Ecore.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TransformGraph2Ecore.java new file mode 100644 index 00000000..7770b82d --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/TransformGraph2Ecore.java @@ -0,0 +1,97 @@ +package org.emoflon.gips.debugger.trace; + +import java.util.HashMap; +import java.util.Map; + +/** + * Helper class for converting {@link TraceGraph TraceGraphs} to + * {@link TraceModelPackage Ecore trace models} + * + * @see TransformGraph2Ecore + */ +public final class TransformGraph2Ecore { + private TransformGraph2Ecore() { + + } + + public static Root buildModelFromGraph(TraceGraph graph) { + var helper = new Helper(); + helper.add(graph); + return helper.get(); + } + + private static final class Helper { + private final TraceModelFactory factory = TraceModelFactory.eINSTANCE; + private final Map models = new HashMap<>(); + private final Root root = factory.createRoot(); + + public void add(TraceGraph graph) { + + for (var gSrcRef : graph.getAllModelReferenceIds()) { + var mSrcRef = getOrCreateModel(graph.getModelReference(gSrcRef, false)); + + for (var gDstRef : graph.getTargetModelIds(gSrcRef)) { + var mDstRef = getOrCreateModel(graph.getModelReference(gDstRef, false)); + + var gModelLink = graph.getLink(gSrcRef, gDstRef); + var mModelLink = getOrCreateModelTransformation(mSrcRef, mDstRef); + + for (var gSrcNode : gModelLink.getSourceNodeIds()) { + var mSrcNode = getOrCreateElement(mSrcRef, gSrcNode); + + for (var gDstNode : gModelLink.resolveFromSrcToDst(gSrcNode)) { + var mDstNode = getOrCreateElement(mDstRef, gDstNode); + + var mNodeLink = factory.createElementTransformation(); + mNodeLink.setRoot(mModelLink); + mNodeLink.setSource(mSrcNode); + mNodeLink.setTarget(mDstNode); + } + } + } + } + } + + public Root get() { + return root; + } + + private ModelReference getOrCreateModel(TraceModelReference gRef) { + var mRef = models.computeIfAbsent(gRef.getModelId(), key -> { + var modelRef = factory.createModelReference(); + modelRef.setModelId(key); + root.getModels().add(modelRef); + return modelRef; + }); + return mRef; + } + + private ModelTransformation getOrCreateModelTransformation(ModelReference mSrcRef, ModelReference mDstRef) { + for (var mt : mSrcRef.getTransformsTo()) { + if (mt.getTarget() == mDstRef) { + return mt; + } + } + + var modelTransformation = factory.createModelTransformation(); + modelTransformation.setSource(mSrcRef); + modelTransformation.setTarget(mDstRef); + return modelTransformation; + } + + private ElementReference getOrCreateElement(ModelReference model, String elementId) { + for (var element : model.getElements()) { + if (element.getElementId().equals(elementId)) { + return element; + } + } + + var element = factory.createElementReference(); + element.setElementId(elementId); + element.setRoot(model); + return element; + } + + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveEcore2Id.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveEcore2Id.java new file mode 100644 index 00000000..47447353 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveEcore2Id.java @@ -0,0 +1,43 @@ +package org.emoflon.gips.debugger.trace.resolver; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; + +/** + * The resolver returns the {@link URI#fragment() fragment path} of a given + * {@link EObject} as the Id of that object. The fragment path is used to + * identify a specific element within the containing resource. + * + * @see ResolveId2Ecore + * @see EcoreUtil#getURI(EObject) + */ +public final class ResolveEcore2Id implements ResolveElement2Id { + + public final static ResolveEcore2Id INSTANCE = new ResolveEcore2Id(); + private EObject root; + + public ResolveEcore2Id() { + + } + + /** + * + * @param root optional, can be null. The root is used to create a relative + * fragment path, starting from there. + */ + public ResolveEcore2Id(EObject root) { + this.root = root; + } + + @Override + public String resolve(final EObject object) { + if (root != null) { + return EcoreUtil.getRelativeURIFragmentPath(root, object); + } else { + URI uri = EcoreUtil.getURI(object); + return uri.fragment(); + } + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveElement2Id.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveElement2Id.java new file mode 100644 index 00000000..a0de6d82 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveElement2Id.java @@ -0,0 +1,11 @@ +package org.emoflon.gips.debugger.trace.resolver; + +/** + * A general purpose interface to handle the mapping of a given element of type T to a unique id. + * For every instance of this interface, there needs to be a matching instance of {@link ResolveId2Element}, which can map the id back to the (functionally) same element. + */ +public interface ResolveElement2Id { + + String resolve(T object); + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Ecore.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Ecore.java new file mode 100644 index 00000000..deeb649c --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Ecore.java @@ -0,0 +1,29 @@ +package org.emoflon.gips.debugger.trace.resolver; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; + +/** + * The resolver returns a {@link EObject} for a given {@link URI#fragment() + * fragment path}. To do so, the resolver is constructed based on an + * {@link EObject} which acts as the root/container of the fragment path. + * + * @see ResolveEcore2Id + * @see EcoreUtil#getEObject(EObject, String) + */ +public class ResolveId2Ecore implements ResolveId2Element { + + private final EObject root; + + public ResolveId2Ecore(EObject root) { + this.root = root; + } + + @Override + public EObject resolve(final String id) { + var element = EcoreUtil.getEObject(root, id); + return element; + } + +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Element.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Element.java new file mode 100644 index 00000000..4dfe8656 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Element.java @@ -0,0 +1,5 @@ +package org.emoflon.gips.debugger.trace.resolver; + +public interface ResolveId2Element { + T resolve(String id); +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Identity.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Identity.java new file mode 100644 index 00000000..a3771fa5 --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveId2Identity.java @@ -0,0 +1,10 @@ +package org.emoflon.gips.debugger.trace.resolver; + +public final class ResolveId2Identity implements ResolveId2Element { + public static final ResolveId2Identity INSTANCE = new ResolveId2Identity(); + + @Override + public String resolve(String id) { + return id; + } +} diff --git a/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveIdentity2Id.java b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveIdentity2Id.java new file mode 100644 index 00000000..07b43acb --- /dev/null +++ b/org.emoflon.gips.debugger.trace/src/org/emoflon/gips/debugger/trace/resolver/ResolveIdentity2Id.java @@ -0,0 +1,12 @@ +package org.emoflon.gips.debugger.trace.resolver; + +public final class ResolveIdentity2Id implements ResolveElement2Id { + + public final static ResolveIdentity2Id INSTANCE = new ResolveIdentity2Id(); + + @Override + public String resolve(final String object) { + return object.toString(); + } + +} diff --git a/org.emoflon.gips.debugger/.classpath b/org.emoflon.gips.debugger/.classpath new file mode 100644 index 00000000..81fe078c --- /dev/null +++ b/org.emoflon.gips.debugger/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.emoflon.gips.debugger/.gitignore b/org.emoflon.gips.debugger/.gitignore new file mode 100644 index 00000000..3bdd7da5 --- /dev/null +++ b/org.emoflon.gips.debugger/.gitignore @@ -0,0 +1,61 @@ +.metadata +bin/ +test-bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project \ No newline at end of file diff --git a/org.emoflon.gips.debugger/.project b/org.emoflon.gips.debugger/.project new file mode 100644 index 00000000..0e4b3ca3 --- /dev/null +++ b/org.emoflon.gips.debugger/.project @@ -0,0 +1,28 @@ + + + org.emoflon.gips.debugger + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.emoflon.gips.debugger/META-INF/MANIFEST.MF b/org.emoflon.gips.debugger/META-INF/MANIFEST.MF new file mode 100644 index 00000000..09957d1d --- /dev/null +++ b/org.emoflon.gips.debugger/META-INF/MANIFEST.MF @@ -0,0 +1,29 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Gips::Debugger +Bundle-SymbolicName: org.emoflon.gips.debugger;singleton:=true +Bundle-Vendor: Real-Time Systems Lab - TU Darmstadt +Bundle-Version: 1.0.0.qualifier +Export-Package: org.emoflon.gips.debugger.api, + org.emoflon.gips.debugger.utility +Import-Package: com.google.common.base +Bundle-Activator: org.emoflon.gips.debugger.TracePlugin +Require-Bundle: org.eclipse.ui, + org.eclipse.core.runtime, + org.eclipse.xtext.ui, + org.eclipse.emf.common, + org.eclipse.ui.workbench, + org.eclipse.ui.ide, + org.eclipse.e4.core.contexts, + org.eclipse.e4.core.di.annotations, + javax.inject, + org.eclipse.emf.ecore.editor, + org.eclipse.emf.edit.ui, + org.eclipse.emf.ecore, + org.emoflon.gips.gipsl, + org.eclipse.jface, + org.emoflon.gips.debugger.cplexlp;bundle-version="[1.0.0,2.0.0)", + org.emoflon.gips.debugger.trace;bundle-version="[1.0.0,2.0.0)";visibility:=reexport +Bundle-RequiredExecutionEnvironment: JavaSE-21 +Automatic-Module-Name: org.emoflon.gips.debugger +Bundle-ActivationPolicy: lazy diff --git a/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-disabled.png b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-disabled.png new file mode 100644 index 00000000..992d5465 Binary files /dev/null and b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-disabled.png differ diff --git a/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-off.png b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-off.png new file mode 100644 index 00000000..eef01ac0 Binary files /dev/null and b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-off.png differ diff --git a/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-on.png b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-on.png new file mode 100644 index 00000000..6c37dfea Binary files /dev/null and b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon-on.png differ diff --git a/org.emoflon.gips.debugger/assets/toggle-visualisation-icon.inkscape.svg b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon.inkscape.svg new file mode 100644 index 00000000..980bd017 --- /dev/null +++ b/org.emoflon.gips.debugger/assets/toggle-visualisation-icon.inkscape.svg @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger/build.properties b/org.emoflon.gips.debugger/build.properties new file mode 100644 index 00000000..e9863e28 --- /dev/null +++ b/org.emoflon.gips.debugger/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml diff --git a/org.emoflon.gips.debugger/plugin.xml b/org.emoflon.gips.debugger/plugin.xml new file mode 100644 index 00000000..4ed890a1 --- /dev/null +++ b/org.emoflon.gips.debugger/plugin.xml @@ -0,0 +1,151 @@ + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/StartUp.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/StartUp.java new file mode 100644 index 00000000..fa2cdc65 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/StartUp.java @@ -0,0 +1,12 @@ +package org.emoflon.gips.debugger; + +import org.eclipse.ui.IStartup; + +/** + * only used for early plugin activation + */ +public class StartUp implements IStartup { + @Override + public void earlyStartup() { + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/TracePlugin.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/TracePlugin.java new file mode 100644 index 00000000..da1d3376 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/TracePlugin.java @@ -0,0 +1,118 @@ +package org.emoflon.gips.debugger; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.preferences.ConfigurationScope; +import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.eclipse.ui.preferences.ScopedPreferenceStore; +import org.emoflon.gips.debugger.api.ITraceManager; +import org.emoflon.gips.debugger.service.TraceManager; +import org.emoflon.gips.debugger.service.TraceRemoteService; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** + * The activator class controls the plug-in life cycle + */ +public final class TracePlugin extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.emoflon.gips.debugger"; //$NON-NLS-1$ + + // synchronization lock + private static final Object syncLock = new Object(); + + // The shared instance + private static TracePlugin INSTANCE; + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static TracePlugin getInstance() { + return INSTANCE; + } + +// public static void log(IStatus status) { +// getInstance().getLog().log(status); +// } + + private TraceManager traceManager; + private ServiceRegistration traceManagerRegistration; + + private TraceRemoteService remoteService; + + private ScopedPreferenceStore preferenceStore; + + public TracePlugin() { + Assert.isTrue(INSTANCE == null); + INSTANCE = this; + } + + @Override + public void start(BundleContext bundleContext) throws Exception { + super.start(bundleContext); + + traceManager = new TraceManager(); + traceManager.initialize(); + +// var eclipseContext = PlatformUI.getWorkbench().getService(IEclipseContext.class); +// eclipseContext.set(ITraceManager.class, traceManager); +// var eclipseContext = EclipseContextFactory.getServiceContext(bundleContext); +// eclipseContext.set(ITraceManager.class, traceManager); + traceManagerRegistration = getBundle().getBundleContext().registerService(ITraceManager.class, traceManager, + null); + +// if(eclipseContext!=null) { +// try { +// var test = ContextInjectionFactory.make(InjectionTest.class, eclipseContext); +// } catch (Exception e) { +// +// } +// } + +// var eclipseContext = EclipseContextHelper.getActiveContext(); +// this.watcher = ContextInjectionFactory.make(EditorWatcher.class, eclipseContext); + + remoteService = new TraceRemoteService(); + remoteService.initialize(); + } + + @Override + public void stop(BundleContext context) throws Exception { + traceManagerRegistration.unregister(); + + remoteService.dispose(); + traceManager.dispose(); + + super.stop(context); + } + + @Override + public ScopedPreferenceStore getPreferenceStore() { + if (preferenceStore == null) { + synchronized (syncLock) { + if (preferenceStore == null) { + preferenceStore = new ScopedPreferenceStore(InstanceScope.INSTANCE, PLUGIN_ID); + preferenceStore.setSearchContexts( + new IScopeContext[] { InstanceScope.INSTANCE, ConfigurationScope.INSTANCE }); + + } + } + } + return this.preferenceStore; + } + + /** + * Shareable singleton instance, not intended to be accessible from outside the + * plugin + * + * @see ITraceManager#getInstance() + */ + public TraceManager getTraceManager() { + return traceManager; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/AnnotationAndPosition.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/AnnotationAndPosition.java new file mode 100644 index 00000000..ed2a4b74 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/AnnotationAndPosition.java @@ -0,0 +1,46 @@ +package org.emoflon.gips.debugger.annotation; + +import java.util.Objects; + +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.source.Annotation; + +public final class AnnotationAndPosition { + public final Annotation annotation; + public final Position position; + + public AnnotationAndPosition(Annotation annotation, Position position) { + this.annotation = Objects.requireNonNull(annotation, "annotation"); + this.position = Objects.requireNonNull(position, "position"); + } + + public Annotation getAnnotation() { + return annotation; + } + + public Position getPosition() { + return position; + } + + @Override + public String toString() { + return "AnnotationAndPosition [annotation=" + annotation + ", position=" + position + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(annotation, position); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AnnotationAndPosition other = (AnnotationAndPosition) obj; + return Objects.equals(annotation, other.annotation) && Objects.equals(position, other.position); + } +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/AnnotationMarkerData.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/AnnotationMarkerData.java new file mode 100644 index 00000000..673c0533 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/AnnotationMarkerData.java @@ -0,0 +1,20 @@ +package org.emoflon.gips.debugger.annotation; + +public final class AnnotationMarkerData { + + public int offset; + public int length; + + public String comment; + + public AnnotationMarkerData(int offset, int length) { + this(offset, length, null); + } + + public AnnotationMarkerData(int offset, int length, String comment) { + this.offset = offset; + this.length = length; + this.comment = comment; + } + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/HelperTraceAnnotation.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/HelperTraceAnnotation.java new file mode 100644 index 00000000..a08fa195 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/HelperTraceAnnotation.java @@ -0,0 +1,99 @@ +package org.emoflon.gips.debugger.annotation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; + +public final class HelperTraceAnnotation { + private HelperTraceAnnotation() { + + } + + public static interface CommentMerger { + String merge(String previousComment, String nextComment); + } + + /** + * Relevant extension points + *
    + *
  • org.eclipse.core.resources.markers + *
  • org.eclipse.ui.editors.annotationTypes + *
  • org.eclipse.ui.editors.markerAnnotationSpecification + *
+ */ + public static String TRACE_MARKER_ID = "org.emoflon.gips.trace.marker.link"; + + public static IMarker createMarker(IResource resource) throws CoreException { + if (resource.exists() && resource.getProject().isOpen()) { + return resource.createMarker(TRACE_MARKER_ID); + } + return null; + } + + public static IMarker createMarker(IResource resource, AnnotationMarkerData data) throws CoreException { + return HelperTraceAnnotation.createMarker(resource, data.offset, data.length, data.comment); + } + + public static IMarker createMarker(IResource resource, int offset, int length, String comment) + throws CoreException { + + final var parameters = new HashMap(); + parameters.put(IMarker.CHAR_START, offset); + parameters.put(IMarker.CHAR_END, offset + length); + if (comment != null && comment.length() > 0) { + parameters.put(IMarker.MESSAGE, comment); + } + + if (resource.exists() && resource.getProject().isOpen()) { + return resource.createMarker(TRACE_MARKER_ID, parameters); + } + return null; + } + + public static void deleteAllMarkers(IResource resource) throws CoreException { + if (resource.exists() && resource.getProject().isOpen()) { + resource.deleteMarkers(HelperTraceAnnotation.TRACE_MARKER_ID, false, IResource.DEPTH_INFINITE); + } + } + + public static List mergeMarkers(Collection markers, int delta, + HelperTraceAnnotation.CommentMerger commentMerger) { + + if (delta < 0) { + throw new IllegalArgumentException("delta must be greater than or equal to zero"); + } + + if (markers.isEmpty()) { + return Collections.emptyList(); + } + + if (markers.size() == 1) { + return Collections.singletonList(markers.iterator().next()); + } + + var sorted = new ArrayList<>(markers); + sorted.sort((a, b) -> a.offset < b.offset ? -1 : 1); + + var result = new ArrayList(); + result.add(new AnnotationMarkerData(sorted.get(0).offset, sorted.get(0).length, sorted.get(0).comment)); + + for (var i = 1; i < sorted.size(); ++i) { + var lastMarker = result.get(result.size() - 1); + var nextMarker = sorted.get(i + 1); + if (nextMarker.offset + delta < lastMarker.offset + lastMarker.length) { + lastMarker.length = nextMarker.offset + nextMarker.length - lastMarker.offset; + lastMarker.comment = commentMerger.merge(lastMarker.comment, nextMarker.comment); + } else { + result.add(new AnnotationMarkerData(nextMarker.offset, nextMarker.length, nextMarker.comment)); + } + } + + return result; + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/TraceAnnotationImageProvider.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/TraceAnnotationImageProvider.java new file mode 100644 index 00000000..9fd8a84e --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/annotation/TraceAnnotationImageProvider.java @@ -0,0 +1,32 @@ +package org.emoflon.gips.debugger.annotation; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.texteditor.IAnnotationImageProvider; + +public class TraceAnnotationImageProvider implements IAnnotationImageProvider { + + public TraceAnnotationImageProvider() { + // TODO Auto-generated constructor stub + } + + @Override + public Image getManagedImage(Annotation annotation) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getImageDescriptorId(Annotation annotation) { + // TODO Auto-generated method stub + return null; + } + + @Override + public ImageDescriptor getImageDescriptor(String imageDescritporId) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/IEditorTracker.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/IEditorTracker.java new file mode 100644 index 00000000..1c220808 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/IEditorTracker.java @@ -0,0 +1,16 @@ +package org.emoflon.gips.debugger.api; + +import org.eclipse.ui.IEditorPart; + +public interface IEditorTracker { + + /** + * + * @param editor + * @return True, if registration was successful. + */ + boolean registerEditor(IEditorPart editor); + + void unregisterEditor(IEditorPart editor); + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ILPTraceKeywords.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ILPTraceKeywords.java new file mode 100644 index 00000000..1f4d0b70 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ILPTraceKeywords.java @@ -0,0 +1,52 @@ +package org.emoflon.gips.debugger.api; + +import java.util.Objects; + +public final class ILPTraceKeywords { + private ILPTraceKeywords() { + + } + + public static final String TYPE_VALUE_DELIMITER = "::"; + public static final String TYPE_CONSTRAINT = "constraint"; + public static final String TYPE_MAPPING = "mapping"; + public static final String TYPE_CONSTRAINT_VAR = "constraint-var"; + public static final String TYPE_FUNCTION = "function"; + public static final String TYPE_FUNCTION_VAR = "function-var"; + public static final String TYPE_OBJECTIVE = "objective"; + public static final String TYPE_VARIABLE = "variable"; + + public static String buildElementId(String type, String elementName) { + Objects.requireNonNull(type); + elementName = elementName != null ? elementName : ""; + return type + TYPE_VALUE_DELIMITER + elementName; + } + + public static record TypeValuePair(String type, String value) { + + @Override + public String toString() { + return getElementId(); + } + + public String getElementId() { + return buildElementId(type, value); + } + } + + public static TypeValuePair getTypeAndValue(String text) { + String type = null; + String value = null; + + var delimiter = text.indexOf(ILPTraceKeywords.TYPE_VALUE_DELIMITER); + if (delimiter < 0) { + type = ""; + value = text; + } else { + type = text.substring(0, delimiter); + value = text.substring(delimiter + ILPTraceKeywords.TYPE_VALUE_DELIMITER.length()); + } + + return new TypeValuePair(type, value); + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/IModelLink.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/IModelLink.java new file mode 100644 index 00000000..a6597c49 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/IModelLink.java @@ -0,0 +1,15 @@ +package org.emoflon.gips.debugger.api; + +import java.util.Collection; + +public interface IModelLink { + + String getStartModelId(); + + String getEndModelId(); + + boolean isResolved(); + + Collection resolveElementsFromSrcToDst(Collection elementIds); + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceContext.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceContext.java new file mode 100644 index 00000000..ab2874d6 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceContext.java @@ -0,0 +1,65 @@ +package org.emoflon.gips.debugger.api; + +import java.util.Collection; +import java.util.Set; + +import org.eclipse.emf.common.util.URI; +import org.emoflon.gips.debugger.api.event.ITraceSelectionListener; +import org.emoflon.gips.debugger.api.event.ITraceUpdateListener; +import org.emoflon.gips.debugger.trace.TraceModelLink; + +public interface ITraceContext { + + public ITraceManager getTraceManager(); + + /** + * Returns the id of this context + * + * @return the id of this context + */ + String getContextId(); + + void addListener(ITraceSelectionListener listener); + + void removeListener(ITraceSelectionListener listener); + + void addListener(ITraceUpdateListener listener); + + void removeListener(ITraceUpdateListener listener); + + boolean hasTraceFor(String modelId); + + Set getSourceModels(String modelId); + + Set getTargetModels(String modelId); + + Set getAllModels(); + + /** + * + * + * @param srcModelId Model from which the selection originates + * @param elementIds which elements have been selected + * @throws TraceModelNotFoundException + */ + void selectElementsByTrace(String modelId, Collection elementIds) throws TraceModelNotFoundException; + + /** + * Returns a link between {@code startModelId} and {@code targetModelId}. With + * this link it's possible to resolve elements from the start model to the end + * model. + * + * @param startModelId + * @param targetModelId + * @return + */ + IModelLink getModelChain(String startModelId, String targetModelId); + + Collection resolveElementsByTrace(String startModelId, String endModelId, Collection elements, + boolean bidirectional); + + void updateTraceModel(TraceModelLink traceLink); + + void loadAndUpdateTraceModel(URI fileURI); + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceManager.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceManager.java new file mode 100644 index 00000000..756408d7 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceManager.java @@ -0,0 +1,92 @@ +package org.emoflon.gips.debugger.api; + +import java.util.Collection; +import java.util.Set; + +import org.emoflon.gips.debugger.TracePlugin; +import org.emoflon.gips.debugger.api.event.ITraceManagerListener; +import org.emoflon.gips.debugger.api.event.ITraceSelectionListener; +import org.emoflon.gips.debugger.api.event.ITraceUpdateListener; + +public interface ITraceManager { + + /** + * Shared singleton instance + */ + public static ITraceManager getInstance() { + return TracePlugin.getInstance().getTraceManager(); + } + + /** + * Adds the given listener for manager related events to this manager. Has no + * effect if the given listener is already registered. Callbacks may or may not + * run on the UI-Thread. + * + * @param listener the listener, may not be null + * @see #removeTraceManagerListener(ITraceManagerListener) + */ + void addTraceManagerListener(ITraceManagerListener listener); + + /** + * Removes the given listener + * + * @param listener the listener, can be null + */ + void removeTraceManagerListener(ITraceManagerListener listener); + + void removeListener(ITraceSelectionListener listener); + + void removeListener(ITraceUpdateListener listener); + + @Deprecated + void addListener(String contextId, ITraceSelectionListener listener); + + @Deprecated + void removeListener(String contextId, ITraceSelectionListener listener); + + @Deprecated + void addListener(String contextId, ITraceUpdateListener listener); + + @Deprecated + void removeListener(String contextId, ITraceUpdateListener listener); + + void selectElementsByTraceModel(String contextId, String modelId, Collection selection) + throws TraceModelNotFoundException; + + /** + * Allows to add or remove an editor from this manager + */ + IEditorTracker getEditorTracker(); + + /** + * Returns a {@link ITraceContext} for the given id. The id must be the name of + * an accessible Eclipse project. + * + * @param contextId name of an Eclipse project, may not be null + * @return a {@link ITraceContext} + * @exception IllegalArgumentException if there is no accessible Eclipse project + * with the given name. + * @see #doesContextExist(String) + * @see #getAvailableContextIds() + */ + ITraceContext getContext(String contextId); + + /** + * + * @param contextId name of an Eclipse project + * @return true if the given id is valid + * @see #getContext(String) + */ + boolean doesContextExist(String contextId); + + boolean isVisualisationActive(); + + /** + * + * @return a {@link Set} of valid context ids + * @see #getContext(String) + * @see #doesContextExist(String) + */ + Set getAvailableContextIds(); + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceRemoteService.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceRemoteService.java new file mode 100644 index 00000000..21826a58 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/ITraceRemoteService.java @@ -0,0 +1,32 @@ +package org.emoflon.gips.debugger.api; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +import org.emoflon.gips.debugger.trace.TraceModelLink; + +/** + * RMI service interface. This interface provides methods to access a single, + * locally running instance of this plugin and can be used to exchange + * information. + *

+ * If available (see preferences), this service can be accessed as follows: + * + *

+ * var service = (ITraceRemoteService) LocateRegistry.getRegistry(port).lookup("ITraceRemoteService");
+ * 
+ * + */ +public interface ITraceRemoteService extends Remote { + + public static final String SERVICE_NAME = "ITraceRemoteService"; + + /** + * Updates the trace data of a given Eclipse project. + * + * @param contextId Eclipse project name this trace belongs to + * @param traceLink trace between two models + * @throws RemoteException + */ + void updateTraceModel(String contextId, TraceModelLink traceLink) throws RemoteException; +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/Intermediate2IlpTracer.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/Intermediate2IlpTracer.java new file mode 100644 index 00000000..1733ba73 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/Intermediate2IlpTracer.java @@ -0,0 +1,117 @@ +package org.emoflon.gips.debugger.api; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.emoflon.gips.debugger.trace.EcoreWriter; +import org.emoflon.gips.debugger.trace.TraceGraph; +import org.emoflon.gips.debugger.trace.TraceMap; +import org.emoflon.gips.debugger.trace.TraceModelLink; +import org.emoflon.gips.debugger.trace.TransformGraph2Ecore; +import org.emoflon.gips.debugger.trace.resolver.ResolveEcore2Id; +import org.emoflon.gips.debugger.trace.resolver.ResolveIdentity2Id; + +public class Intermediate2IlpTracer { + + private final TraceMap mappings = new TraceMap<>(); + + private int rmiServicePort = 2842; + + private String intermediateModelId; + private String lpModelId; + private boolean tracingEnabled; + + public Intermediate2IlpTracer() { + + } + + public TraceMap getMapping() { + return mappings; + } + + public void map(final EObject src, final String dst) { + mappings.map(src, dst); + } + + public void setRMIPort(int port) { + this.rmiServicePort = port; + } + + public void postTraceToRMIService() { + Path workingDirectory = Paths.get("").toAbsolutePath(); + // this should, in theory, be the eclipse project name + String contextId = workingDirectory.getFileName().toString(); + + TraceMap mapping = TraceMap.normalize(mappings, ResolveEcore2Id.INSTANCE, + ResolveIdentity2Id.INSTANCE); + TraceModelLink link = new TraceModelLink(intermediateModelId, lpModelId, mapping); + + try { + ITraceRemoteService service = (ITraceRemoteService) LocateRegistry.getRegistry(rmiServicePort) + .lookup(ITraceRemoteService.SERVICE_NAME); + service.updateTraceModel(contextId, link); + } catch (RemoteException | NotBoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public void computeGipsModelId(URI modelUri) { + Path intermediatePath; + if (modelUri.isPlatform()) + intermediatePath = Path.of(modelUri.toPlatformString(true)); + else + intermediatePath = Path.of(modelUri.toFileString()); + + intermediateModelId = computeModelIdFromPath(intermediatePath); + } + + public void computeLpModelId(String lpPath) { + lpModelId = computeModelIdFromPath(Path.of(lpPath)); + } + + public void enableTracing(boolean enableTracing) { + this.tracingEnabled = enableTracing; + } + + public boolean isTracingEnabled() { + return this.tracingEnabled; + } + + public void saveTraceAsGraph(Path filePath) { + var graph = buildGraph(); + var root = TransformGraph2Ecore.buildModelFromGraph(graph); + var uri = URI.createFileURI(filePath.toAbsolutePath().toString()); + EcoreWriter.saveModel(root, uri); + } + + private String computeModelIdFromPath(Path modelPath) { + return computeModelIdFromPath(Paths.get("").toAbsolutePath(), modelPath); + } + + private String computeModelIdFromPath(Path root, Path modelPath) { + if (!modelPath.isAbsolute()) + modelPath = root.resolve(modelPath).normalize(); + + Path relativePath = root.relativize(modelPath); // we use the relative path as id + String id = StreamSupport.stream(relativePath.spliterator(), false).map(Path::toString) + .collect(Collectors.joining("/")); // use '/' like IPath.toString + return id; + } + + private TraceGraph buildGraph() { + var graph = new TraceGraph(); + var mapping = TraceMap.normalize(mappings, ResolveEcore2Id.INSTANCE, ResolveIdentity2Id.INSTANCE); + var link = new TraceModelLink(intermediateModelId, lpModelId, mapping); + graph.addOrReplaceTraceLink(link); + return graph; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/TraceModelNotFoundException.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/TraceModelNotFoundException.java new file mode 100644 index 00000000..106942a6 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/TraceModelNotFoundException.java @@ -0,0 +1,23 @@ +package org.emoflon.gips.debugger.api; + +import java.util.Objects; + +public final class TraceModelNotFoundException extends RuntimeException { + + private static final long serialVersionUID = -7965914712842445716L; + + private final String modelId; + + public TraceModelNotFoundException(String modelId, String message) { + super(message); + this.modelId = Objects.requireNonNull(modelId); + } + + public TraceModelNotFoundException(String modelId) { + this(modelId, "Model '" + modelId + "' not found."); + } + + public String getModelId() { + return modelId; + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceManagerListener.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceManagerListener.java new file mode 100644 index 00000000..5bd95efc --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceManagerListener.java @@ -0,0 +1,10 @@ +package org.emoflon.gips.debugger.api.event; + +import java.util.EventListener; + +@FunctionalInterface +public interface ITraceManagerListener extends EventListener { + + void contextChanged(TraceManagerEvent event); + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceSelectionListener.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceSelectionListener.java new file mode 100644 index 00000000..f211bdd1 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceSelectionListener.java @@ -0,0 +1,23 @@ +package org.emoflon.gips.debugger.api.event; + +import java.util.Collection; +import java.util.EventListener; + +import org.emoflon.gips.debugger.api.ITraceContext; + +/** + * Interface for listening to selection events created by the tracing framework. + * + * @see {@link ITraceContext#addListener(ITraceSelectionListener)} + * @see {@link ITraceContext#selectElementsByTrace(String, Collection)} + */ +@FunctionalInterface +public interface ITraceSelectionListener extends EventListener { + /** + * Notifies this listener that a selection has been made. + * + * @param event which was raised, contains all relevant data + * @see TraceSelectionEvent + */ + public void selectedByModel(TraceSelectionEvent event); +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceUpdateListener.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceUpdateListener.java new file mode 100644 index 00000000..cdedfb48 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/ITraceUpdateListener.java @@ -0,0 +1,6 @@ +package org.emoflon.gips.debugger.api.event; + +@FunctionalInterface +public interface ITraceUpdateListener { + public void updatedModels(TraceUpdateEvent event); +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceManagerEvent.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceManagerEvent.java new file mode 100644 index 00000000..9957fe50 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceManagerEvent.java @@ -0,0 +1,35 @@ +package org.emoflon.gips.debugger.api.event; + +import java.util.Objects; + +import org.emoflon.gips.debugger.api.ITraceManager; + +public final class TraceManagerEvent { + + public static enum EventType { + NEW, DELETED + } + + private final ITraceManager source; + private final EventType eventType; + private final String contextId; + + public TraceManagerEvent(ITraceManager source, EventType eventType, String contextId) { + this.source = Objects.requireNonNull(source, "source"); + this.eventType = Objects.requireNonNull(eventType, "eventType"); + this.contextId = Objects.requireNonNull(contextId, "contextId"); + } + + public ITraceManager getSource() { + return source; + } + + public EventType getEventType() { + return eventType; + } + + public String getContextId() { + return contextId; + } + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceSelectionEvent.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceSelectionEvent.java new file mode 100644 index 00000000..229a3eba --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceSelectionEvent.java @@ -0,0 +1,55 @@ +package org.emoflon.gips.debugger.api.event; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +import org.emoflon.gips.debugger.api.ITraceContext; + +public final class TraceSelectionEvent { + + private final ITraceContext context; + private final String modelId; + private final Collection elementIds; + + /** + * @param context the context in which the event was raised, may not be null + * @param modelId the model that triggered the event, may not be null + * @param elementIds the elements that have been selected on {@code modelId}. + * Will be stored as an unmodifiable collection and may not be + * null + */ + public TraceSelectionEvent(ITraceContext context, String modelId, Collection elementIds) { + this.context = Objects.requireNonNull(context, "context"); + this.modelId = Objects.requireNonNull(modelId, "modelId"); + this.elementIds = Collections.unmodifiableCollection(Objects.requireNonNull(elementIds, "elementIds")); + } + + /** + * Returns the context in which the event was raised + * + * @return a {@link ITraceContext}, never null + */ + public ITraceContext getContext() { + return context; + } + + /** + * Returns the model that triggered the event + * + * @return a model id, never null + */ + public String getModelId() { + return modelId; + } + + /** + * Returns the elements that have been selected on {@link #getElementIds()} + * + * @return a collection of ids, never null + */ + public Collection getElementIds() { + return elementIds; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceUpdateEvent.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceUpdateEvent.java new file mode 100644 index 00000000..02757ef6 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/api/event/TraceUpdateEvent.java @@ -0,0 +1,27 @@ +package org.emoflon.gips.debugger.api.event; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +import org.emoflon.gips.debugger.api.ITraceContext; + +public final class TraceUpdateEvent { + + private final ITraceContext context; + private final Collection modelIds; + + public TraceUpdateEvent(ITraceContext context, Collection modelIds) { + this.context = Objects.requireNonNull(context, "context"); + this.modelIds = Collections.unmodifiableCollection(Objects.requireNonNull(modelIds, "modelIds")); + } + + public ITraceContext getContext() { + return context; + } + + public Collection getModelIds() { + return modelIds; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/CplexLpEditorTraceConnectionFactory.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/CplexLpEditorTraceConnectionFactory.java new file mode 100644 index 00000000..64cdc4c8 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/CplexLpEditorTraceConnectionFactory.java @@ -0,0 +1,107 @@ +package org.emoflon.gips.debugger.connector; + +import static org.emoflon.gips.debugger.api.ILPTraceKeywords.buildElementId; + +import java.util.ArrayList; +import java.util.Collection; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.emoflon.gips.debugger.api.ILPTraceKeywords; +import org.emoflon.gips.debugger.connector.highlight.CplexAnnotationHighlightStrategy; +import org.emoflon.gips.debugger.cplexLp.ConstraintExpression; +import org.emoflon.gips.debugger.cplexLp.LinearTerm; +import org.emoflon.gips.debugger.cplexLp.NumberLiteral; +import org.emoflon.gips.debugger.cplexLp.ObjectiveExpression; +import org.emoflon.gips.debugger.cplexLp.SectionObjective; +import org.emoflon.gips.debugger.cplexLp.Variable; +import org.emoflon.gips.debugger.cplexLp.VariableDecleration; +import org.emoflon.gips.debugger.cplexLp.VariableRef; +import org.emoflon.gips.debugger.utility.HelperEObjects; +import org.emoflon.gips.debugger.utility.HelperEcoreSelection; + +public class CplexLpEditorTraceConnectionFactory extends XtextEditorTraceConnectionFactory { + + private static final String LANGUAGE_NAME = "org.emoflon.gips.debugger.CplexLp"; + + public CplexLpEditorTraceConnectionFactory() { + super(LANGUAGE_NAME); + } + + @Override + protected CplexEditorTraceConnection createConnection(XtextEditor editor) { + return new CplexEditorTraceConnection(editor); + } + + private static final class CplexEditorTraceConnection extends XtextEditorTraceConnection { + + public CplexEditorTraceConnection(XtextEditor editor) { + super(editor, new CplexAnnotationHighlightStrategy()); + } + + @Override + protected Collection transformSelectionToElementIds(ISelection selection) { + var eObjects = HelperEcoreSelection.selectionToEObjects(editor, selection); + var elementIds = new ArrayList(eObjects.size()); + for (var eObject : eObjects) { + if (eObject instanceof NumberLiteral) { + var variable = ((LinearTerm) eObject.eContainer()).getVariable(); + if (variable == null) { + // TODO + } else { + eObject = variable; + } + } + + if (eObject instanceof Variable variable) { + String variableName = null; + EObject parent = null; + + if (variable instanceof VariableDecleration variableDecleration) { + variableName = variableDecleration.getName(); + } + + if (variable instanceof VariableRef variableRef) { + variableName = variableRef.getRef().getName(); + parent = HelperEObjects.getParentOfType(variableRef, ConstraintExpression.class, + ObjectiveExpression.class); + } + + if (variableName == null) { + continue; + } + + if (parent instanceof ConstraintExpression) { + elementIds.add(buildElementId(ILPTraceKeywords.TYPE_CONSTRAINT_VAR, variableName)); + } else if (parent instanceof ObjectiveExpression) { + elementIds.add(buildElementId(ILPTraceKeywords.TYPE_FUNCTION_VAR, variableName)); + elementIds.add(buildElementId(ILPTraceKeywords.TYPE_OBJECTIVE, "")); + } else { + elementIds.add(buildElementId(ILPTraceKeywords.TYPE_VARIABLE, variableName)); + } + + var delimiter = variableName.lastIndexOf("#"); + var shortVariableName = delimiter < 0 ? variableName : variableName.substring(0, delimiter); + elementIds.add(buildElementId(ILPTraceKeywords.TYPE_MAPPING, shortVariableName)); + + } else if (eObject instanceof ConstraintExpression constraint) { + String name = constraint.getName(); + var delimiter = name.lastIndexOf("_"); + if (delimiter >= 0) { + name = name.substring(0, delimiter); + } + name = buildElementId(ILPTraceKeywords.TYPE_CONSTRAINT, name); + elementIds.add(name); + + } else if (eObject instanceof SectionObjective) { + elementIds.add(buildElementId(ILPTraceKeywords.TYPE_OBJECTIVE, "")); + continue; + } + } + return elementIds; + } + + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceConnection.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceConnection.java new file mode 100644 index 00000000..40701231 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceConnection.java @@ -0,0 +1,292 @@ +package org.emoflon.gips.debugger.connector; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.function.Predicate; + +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IPartService; +import org.eclipse.ui.IPropertyListener; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.ISelectionService; +import org.eclipse.ui.IWorkbenchPartReference; +import org.eclipse.ui.SelectionListenerFactory; +import org.eclipse.ui.SelectionListenerFactory.ISelectionModel; +import org.emoflon.gips.debugger.TracePlugin; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.api.ITraceManager; +import org.emoflon.gips.debugger.api.TraceModelNotFoundException; +import org.emoflon.gips.debugger.api.event.ITraceSelectionListener; +import org.emoflon.gips.debugger.api.event.TraceSelectionEvent; +import org.emoflon.gips.debugger.listener.PartSelectionFilterListener; + +/** + * This class provides a basic implementation to (dis)connect an editor and + * manage selection events. + * + * @param type of editor + */ +public abstract class EditorTraceConnection implements IEditorTraceConnection { + + public static class Predicates { + public static Predicate selectionNotSeenYet = SelectionListenerFactory.Predicates.alreadyDeliveredAnyPart; + public static Predicate fromSelf = model -> model.getCurrentSelectionPart() == model + .getTargetPart(); + } + + protected final EditorTraceJob editorTraceJob = new EditorTraceJob(); + protected final T editor; + protected final IHighlightStrategy highlightStrategy; + + private boolean isInstalled = false; + + private IPartListener2 partListener; + private IPropertyListener propertyListener; + private ISelectionListener partSelectionListener; + private ITraceSelectionListener traceSelectionListener; + + private String modelId = ""; + private String contextId = ""; + + public EditorTraceConnection(T editor, IHighlightStrategy highlightStrategy) { + this.editor = Objects.requireNonNull(editor, "editor"); + this.highlightStrategy = Objects.requireNonNull(highlightStrategy, "highlightStrategy"); + computeContextAndModelId(); + } + + protected void onEditorInputChange() { + computeContextAndModelId(); + } + + @Override + public boolean isConnected() { + return isInstalled; + } + + @Override + public void connect() { + if (isConnected()) + return; + + this.partListener = buildPartListener(); + this.propertyListener = buildPropertyListener(); + this.partSelectionListener = buildPartSelectionListener(); + this.traceSelectionListener = buildTraceSelectionListener(); + + editor.addPropertyListener(propertyListener); + editor.getSite().getService(IPartService.class).addPartListener(partListener); + editor.getSite().getService(ISelectionService.class).addPostSelectionListener(partSelectionListener); + ITraceManager.getInstance().getContext(getContextId()).addListener(traceSelectionListener); + + isInstalled = true; + } + + @Override + public void disconnect() { + if (!isConnected()) + return; + + editor.removePropertyListener(propertyListener); + editor.getSite().getService(IPartService.class).removePartListener(partListener); + editor.getSite().getService(ISelectionService.class).removePostSelectionListener(partSelectionListener); + ITraceManager.getInstance().removeListener(traceSelectionListener); + + this.partListener = null; + this.propertyListener = null; + this.partSelectionListener = null; + this.traceSelectionListener = null; + + isInstalled = false; + } + + private IPartListener2 buildPartListener() { + return new IPartListener2() { + @Override + public void partClosed(IWorkbenchPartReference partRef) { + if (partRef.getPart(false) == editor) + disconnect(); + } + + @Override + public void partVisible(IWorkbenchPartReference partRef) { + if (partRef.getPart(false) == editor) + onPartVisible(); + } + }; + } + + private IPropertyListener buildPropertyListener() { + return (source, propId) -> { + if (IEditorPart.PROP_INPUT == propId) { + onEditorInputChange(); + ITraceManager.getInstance().removeListener(traceSelectionListener); + ITraceManager.getInstance().getContext(getContextId()).addListener(traceSelectionListener); + } + }; + } + + private ISelectionListener buildPartSelectionListener() { + return new PartSelectionFilterListener(editor, (part, selection) -> this.onEditorSelection(selection), + Predicates.fromSelf.and(Predicates.selectionNotSeenYet)); + } + + private ITraceSelectionListener buildTraceSelectionListener() { + return event -> { + onTraceSelection(event); + }; + } + + private void onPartVisible() { + // TODO Auto-generated method stub + + } + + /** + * Called when the selection on the connected editor changes. Must be passed to + * the trace manager. + * + * @param selection editor selection, may be null + */ + private void onEditorSelection(ISelection selection) { + ITraceManager service = TracePlugin.getInstance().getTraceManager(); + if (!service.isVisualisationActive()) + return; + + if (!service.doesContextExist(getContextId())) + return; + + ITraceContext context = service.getContext(getContextId()); + if (!context.hasTraceFor(getModelId())) { + return; + } + + removeEditorHighlights(); + + sendEditorSelectionToTraceManager(context, selection); + } + + /** + * @param context current context for this editor + * @param selection current editor selection, may be null + */ + private void sendEditorSelectionToTraceManager(ITraceContext context, ISelection selection) { + Collection elementIds = Collections.emptySet(); + if (!selection.isEmpty()) { + elementIds = transformSelectionToElementIds(selection); + } + + try { + context.selectElementsByTrace(getModelId(), elementIds); + } catch (TraceModelNotFoundException e) { + // TODO: maybe there is some way to recover +// if (showNoTraceModelError) { +// showNoTraceModelError = false; +// MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "GIPS Trace", +// e.getMessage()); +// } + e.printStackTrace(); + } + } + + private void onTraceSelection(TraceSelectionEvent event) { + if (getModelId().equals(event.getModelId())) + return; + + if (!editor.getSite().getPage().isPartVisible(editor)) + return; + + if (!event.getContext().hasTraceFor(getModelId())) + return; + + if (event.getElementIds().isEmpty()) { + removeEditorHighlights(); + } else { + computeEditorHighlights(event.getContext(), event.getModelId(), event.getElementIds()); + } + } + + private void computeEditorHighlights(ITraceContext context, String remoteModelId, + Collection remoteElementsById) { + editorTraceJob.cancel(); + editorTraceJob.setup(monitor -> highlightStrategy.computeEditorHighlights(editor, context, getModelId(), + remoteModelId, remoteElementsById, monitor)); + editorTraceJob.setPriority(Job.DECORATE); + editorTraceJob.schedule(); + } + + private void removeEditorHighlights() { + editorTraceJob.cancel(); + editorTraceJob.setup(monitor -> highlightStrategy.removeEditorHighlights(editor, monitor)); + editorTraceJob.setPriority(Job.DECORATE); + editorTraceJob.schedule(); + } + + public T getEditor() { + return editor; + } + + public IHighlightStrategy getHighlightStrategy() { + return highlightStrategy; + } + + /** + * The context id, which is usually the name of the eclipse project in which the + * model is located. If there is no context with this id, the connection will + * not process any selection events. + * + * @return context id, never null + */ + public String getContextId() { + return contextId; + } + + /** + * Sets the current context id for this editor. + * + * @param contextId, if there is no context set to empty string, may not be null + */ + protected void setContextId(String contextId) { + this.contextId = Objects.requireNonNull(contextId, "contextId"); + } + + /** + * The id of the model, which is displayed by this editor. If there is no model + * with this id, the connection will not process any selection events. + * + * @return a model id, never null + */ + public String getModelId() { + return modelId; + } + + /** + * Sets the current model id for this editor. + * + * @param modelId if there is no model set to empty string, may not be null + */ + protected void setModelId(String modelId) { + this.modelId = Objects.requireNonNull(modelId, "modelId"); + } + + /** + * This method is called automatically on object instantiation and when the + * editor input changes. Compute context and model id and set them using + * {@link #setContextId(String)} and {@link #setModelId(String)}. + */ + protected abstract void computeContextAndModelId(); + + /** + * Converts a selection of elements into a set of ids based on the model + * presented by the editor. Each id should uniquely identify one of the selected + * elements. + * + * @param selection A selection within the editor + * @return A set of ids. The collection can be empty but not null + */ + protected abstract Collection transformSelectionToElementIds(ISelection selection); + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceConnectionFactory.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceConnectionFactory.java new file mode 100644 index 00000000..224a6ed3 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceConnectionFactory.java @@ -0,0 +1,67 @@ +package org.emoflon.gips.debugger.connector; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.ui.IEditorPart; + +/** + * This class combines multiple {@link IEditorTraceConnectionFactory}s into a + * single factory. + * + * @see #addConnectionFactory(IEditorTraceConnectionFactory) + */ +public final class EditorTraceConnectionFactory implements IEditorTraceConnectionFactory { + + private final List connects = new ArrayList<>(); + + /** + * Adds an additional factory. + */ + public void addConnectionFactory(IEditorTraceConnectionFactory factory) { + Objects.requireNonNull(factory, "factory"); + connects.add(factory); + } + + /** + * {@inheritDoc} + * + *

+ * Calls {@link #canConnect(IEditorPart)} on each added factory, until one of + * them returns true. + */ + @Override + public boolean canConnect(IEditorPart editorPart) { + for (var connector : connects) { + if (connector.canConnect(editorPart)) { + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + * + *

+ * Iterates through all added factories and returns the first successfully + * created {@link IEditorTraceConnection}. + */ + @Override + public IEditorTraceConnection createConnection(IEditorPart editorPart) { + IEditorTraceConnection connection = null; + + for (var connector : connects) { + if (connector.canConnect(editorPart)) { + connection = connector.createConnection(editorPart); + if (connection != null) { + break; + } + } + } + + return connection; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceJob.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceJob.java new file mode 100644 index 00000000..ef4df3b7 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/EditorTraceJob.java @@ -0,0 +1,31 @@ +package org.emoflon.gips.debugger.connector; + +import java.util.Objects; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; + +public class EditorTraceJob extends Job { + + public static interface IEditorTraceJob { + IStatus run(SubMonitor monitor); + } + + private IEditorTraceJob job; + + public EditorTraceJob() { + super("GIPS trace visualisation task"); + } + + public void setup(IEditorTraceJob job) { + this.job = Objects.requireNonNull(job, "job"); + } + + @Override + protected IStatus run(IProgressMonitor eclipseMonitor) { + return job.run(SubMonitor.convert(eclipseMonitor)); + } + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/GenericXmiEditorTraceConnectionFactory.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/GenericXmiEditorTraceConnectionFactory.java new file mode 100644 index 00000000..2a0c7164 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/GenericXmiEditorTraceConnectionFactory.java @@ -0,0 +1,146 @@ +package org.emoflon.gips.debugger.connector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.presentation.EcoreEditor; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.edit.ui.util.EditUIUtil; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.ui.IEditorPart; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.trace.resolver.ResolveEcore2Id; +import org.emoflon.gips.debugger.utility.HelperEclipse; + +public class GenericXmiEditorTraceConnectionFactory implements IEditorTraceConnectionFactory { + + @Override + public boolean canConnect(IEditorPart editorPart) { + return editorPart instanceof EcoreEditor; + } + + @Override + public XmiEditorTraceConnection createConnection(IEditorPart editorPart) { + return new XmiEditorTraceConnection((EcoreEditor) editorPart); + } + + private static final class XmiEditorTraceConnection extends EditorTraceConnection { + + public XmiEditorTraceConnection(EcoreEditor editor) { + this(editor, new EcoreHighlightStrategy()); + } + + private XmiEditorTraceConnection(EcoreEditor editor, EcoreHighlightStrategy highlightStrategy) { + super(editor, highlightStrategy); + } + + @Override + protected void computeContextAndModelId() { + URI uri = EditUIUtil.getURI(editor.getEditorInput(), + editor.getEditingDomain().getResourceSet().getURIConverter()); + + URI modelURI = HelperEclipse.toPlatformURI(uri); + IProject project = HelperEclipse.tryAndGetProject(modelURI); + IPath relativeFilePath = IPath.fromOSString(modelURI.toPlatformString(true)).removeFirstSegments(1); + + getHighlightStrategy().setModelURI(modelURI); + + setContextId(project.getName()); + setModelId(relativeFilePath.toString()); + } + + @Override + public EcoreHighlightStrategy getHighlightStrategy() { + return (EcoreHighlightStrategy) super.getHighlightStrategy(); + } + + @Override + protected Collection transformSelectionToElementIds(ISelection iSelection) { + if (iSelection instanceof TreeSelection treeSelection) { + EcoreHighlightStrategy strategy = getHighlightStrategy(); + Collection elementIds = new ArrayList(); + + for (Object selection : treeSelection) { + if (selection instanceof EObject eObject) { + Resource resource = eObject.eResource(); + if (resource != null && !strategy.isSameResourceURI(resource.getURI())) + continue; + + String elementId = ResolveEcore2Id.INSTANCE.resolve(eObject); + elementIds.add(elementId); + } + } + return elementIds; + } + return Collections.emptySet(); + } + } + + private static final class EcoreHighlightStrategy implements IHighlightStrategy { + + private URI modelURI; + + @Override + public IStatus removeEditorHighlights(EcoreEditor editor, SubMonitor monitor) { + return Status.OK_STATUS; + } + + @Override + public IStatus computeEditorHighlights(EcoreEditor editor, ITraceContext context, String localModelId, + String remoteModelId, Collection remoteElementIds, SubMonitor monitor) { + + Collection localElementIds = context.resolveElementsByTrace(remoteModelId, localModelId, + remoteElementIds, true); + highlightElementsByUriFragment(editor, localElementIds); + + return Status.OK_STATUS; + } + + private void highlightElementsByUriFragment(EcoreEditor editor, Collection uriFragments) { + if (uriFragments.isEmpty()) + return; + + ResourceSet resourceSet = editor.getEditingDomain().getResourceSet(); + Collection targetSelection = new ArrayList(uriFragments.size()); + + for (Resource resource : resourceSet.getResources()) { + if (!isSameResourceURI(resource.getURI())) + continue; + + for (String uriFragment : uriFragments) { + EObject e = resource.getEObject(uriFragment); + if (e != null) + targetSelection.add(e); + } + + break; + } + + editor.setSelectionToViewer(targetSelection); + } + + public boolean isSameResourceURI(URI uri) { + URI relativeUri = HelperEclipse.toPlatformURI(uri); + return getModelURI().equals(relativeUri); + } + + public URI getModelURI() { + return modelURI; + } + + public void setModelURI(URI modelURI) { + this.modelURI = modelURI; + } + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/GipslEditorTraceConnectionFactory.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/GipslEditorTraceConnectionFactory.java new file mode 100644 index 00000000..1dde1a15 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/GipslEditorTraceConnectionFactory.java @@ -0,0 +1,72 @@ +package org.emoflon.gips.debugger.connector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.emoflon.gips.debugger.connector.highlight.URIAnotationHighlightStrategy; +import org.emoflon.gips.debugger.trace.resolver.ResolveEcore2Id; +import org.emoflon.gips.debugger.utility.HelperEcoreSelection; +import org.emoflon.gips.gipsl.gipsl.GipsConstraint; +import org.emoflon.gips.gipsl.gipsl.GipsLinearFunction; +import org.emoflon.gips.gipsl.gipsl.GipsMapping; +import org.emoflon.gips.gipsl.gipsl.GipsObjective; + +public final class GipslEditorTraceConnectionFactory extends XtextEditorTraceConnectionFactory { + + private static final String LANGUAGE_NAME = "org.emoflon.gips.gipsl.Gipsl"; + + public GipslEditorTraceConnectionFactory() { + super(LANGUAGE_NAME); + } + + @Override + protected GipslEditorTraceConnection createConnection(XtextEditor editor) { + return new GipslEditorTraceConnection(editor); + } + + private static final class GipslEditorTraceConnection extends XtextEditorTraceConnection { + + public GipslEditorTraceConnection(XtextEditor editor) { + super(editor, new URIAnotationHighlightStrategy()); + } + + @Override + protected Collection transformSelectionToElementIds(ISelection selection) { + List eObjects = HelperEcoreSelection.selectionToEObjects(editor, selection); + List relevantEObjets = new ArrayList<>(eObjects.size()); + for (var eObject : eObjects) { + var relevantEObject = findRelevantObject(eObject); + if (relevantEObject != null) { + relevantEObjets.add(relevantEObject); + } else { + relevantEObjets.add(eObject); + } + } + List elementIds = relevantEObjets.stream().map(ResolveEcore2Id.INSTANCE::resolve).toList(); + return elementIds; + } + + private EObject findRelevantObject(EObject eObject) { + if (eObject instanceof GipsConstraint) { + return eObject; + } else if (eObject instanceof GipsLinearFunction) { + return eObject; + } else if (eObject instanceof GipsObjective) { + return eObject; + } else if (eObject instanceof GipsMapping) { + return eObject; + } + + if (eObject.eContainer() != null) + return findRelevantObject(eObject.eContainer()); + + return null; + } + + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IEditorTraceConnection.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IEditorTraceConnection.java new file mode 100644 index 00000000..df496880 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IEditorTraceConnection.java @@ -0,0 +1,16 @@ +package org.emoflon.gips.debugger.connector; + +import org.emoflon.gips.debugger.service.TraceManager; + +/** + * This represents the connection between an editor and a {@link TraceManager}. + * A connected editor will send and receive selection events related to tracing. + * The implementation may vary between different editors + */ +public interface IEditorTraceConnection { + boolean isConnected(); + + void connect(); + + void disconnect(); +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IEditorTraceConnectionFactory.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IEditorTraceConnectionFactory.java new file mode 100644 index 00000000..e6fc3576 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IEditorTraceConnectionFactory.java @@ -0,0 +1,25 @@ +package org.emoflon.gips.debugger.connector; + +import org.eclipse.ui.IEditorPart; + +public interface IEditorTraceConnectionFactory { + /** + * Determines if this factory can create an {@link IEditorTraceConnection} for + * the given part. + * + * @param editorPart for which an {@link IEditorTraceConnection} should be + * created + * @return true, if this factory can create an {@link IEditorTraceConnection} + * for this part + */ + boolean canConnect(IEditorPart editorPart); + + /** + * Creates an {@link IEditorTraceConnection} for the given part. This method + * should only be called, if {@link #canConnect(IEditorPart)} returns true. + * + * @param editorPart + * @return + */ + IEditorTraceConnection createConnection(IEditorPart editorPart); +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IHighlightStrategy.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IHighlightStrategy.java new file mode 100644 index 00000000..eac5276b --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/IHighlightStrategy.java @@ -0,0 +1,17 @@ +package org.emoflon.gips.debugger.connector; + +import java.util.Collection; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.ui.IEditorPart; +import org.emoflon.gips.debugger.api.ITraceContext; + +public interface IHighlightStrategy { + + IStatus computeEditorHighlights(T editor, ITraceContext context, String localModelId, String remoteModelId, + Collection remoteElementIds, SubMonitor monitor); + + IStatus removeEditorHighlights(T editor, SubMonitor monitor); + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/XtextEditorTraceConnection.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/XtextEditorTraceConnection.java new file mode 100644 index 00000000..5c1d882f --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/XtextEditorTraceConnection.java @@ -0,0 +1,33 @@ +package org.emoflon.gips.debugger.connector; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IPath; +import org.eclipse.emf.common.util.URI; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.emoflon.gips.debugger.utility.HelperEclipse; + +public abstract class XtextEditorTraceConnection extends EditorTraceConnection { + + public XtextEditorTraceConnection(XtextEditor editor, IHighlightStrategy highlightStrategy) { + super(editor, highlightStrategy); + } + + @Override + protected void computeContextAndModelId() { + URI uri = editor.getDocument().getResourceURI(); + + IProject project = HelperEclipse.tryAndGetProject(uri); + if (project == null) { // TODO: a way to support files outside of eclipse projects + setContextId(""); + setModelId(""); + return; + } + + IPath filePath = IPath.fromOSString(HelperEclipse.toFileURI(uri).toFileString()); + IPath relativeFilePath = filePath.makeRelativeTo(project.getLocation()); + + setContextId(project.getName()); + setModelId(relativeFilePath.toString()); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/XtextEditorTraceConnectionFactory.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/XtextEditorTraceConnectionFactory.java new file mode 100644 index 00000000..9b475a81 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/XtextEditorTraceConnectionFactory.java @@ -0,0 +1,27 @@ +package org.emoflon.gips.debugger.connector; + +import java.util.Objects; + +import org.eclipse.ui.IEditorPart; +import org.eclipse.xtext.ui.editor.XtextEditor; + +public abstract class XtextEditorTraceConnectionFactory implements IEditorTraceConnectionFactory { + + protected final String languageName; + + public XtextEditorTraceConnectionFactory(String languageName) { + this.languageName = Objects.requireNonNull(languageName, "languageName"); + } + + @Override + public boolean canConnect(IEditorPart editorPart) { + return editorPart instanceof XtextEditor xtextEditor && languageName.equals(xtextEditor.getLanguageName()); + } + + @Override + public IEditorTraceConnection createConnection(IEditorPart editorPart) { + return createConnection((XtextEditor) editorPart); + } + + protected abstract IEditorTraceConnection createConnection(XtextEditor editor); +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/AnnotationBasedHighlightStrategy.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/AnnotationBasedHighlightStrategy.java new file mode 100644 index 00000000..47d1f307 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/AnnotationBasedHighlightStrategy.java @@ -0,0 +1,160 @@ +package org.emoflon.gips.debugger.connector.highlight; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.IAnnotationModelExtension; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.emoflon.gips.debugger.annotation.AnnotationAndPosition; +import org.emoflon.gips.debugger.annotation.HelperTraceAnnotation; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.connector.IHighlightStrategy; + +public abstract class AnnotationBasedHighlightStrategy implements IHighlightStrategy { + + @Override + public IStatus computeEditorHighlights(XtextEditor editor, ITraceContext context, String localModelId, + String remoteModelId, Collection remoteElementIds, SubMonitor monitor) { + + monitor = SubMonitor.convert(monitor, 3); + + monitor.split(1); + Collection elementIds = context.getModelChain(remoteModelId, localModelId) + .resolveElementsFromSrcToDst(remoteElementIds); + + Collection annotationsToAdd = computeNewAnnotations(monitor.split(1), editor, + elementIds); + + Collection annotationsToRemove = getExistingAnnotations(getAnnotationModel(editor)); + + updateEditorUI(monitor.split(1), editor, annotationsToAdd, annotationsToRemove); + + return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS; + } + + @Override + public IStatus removeEditorHighlights(XtextEditor editor, SubMonitor monitor) { + monitor = SubMonitor.convert(monitor, 2); + + monitor.split(1); + Collection annotationsToRemove = getExistingAnnotations(getAnnotationModel(editor)); + + updateEditorUI(monitor.split(1), editor, Collections.emptyList(), annotationsToRemove); + return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS; + } + + private static void updateEditorUI(SubMonitor monitor, ITextEditor editor, + Collection annotationsToAdd, Collection annotationsToRemove) { + + Position revealPosition = findFirstPosition(annotationsToAdd); + + Display.getDefault().syncExec(() -> { + if (monitor.isCanceled()) + return; + + if (revealPosition != null && editor instanceof XtextEditor xtextEditor) + xtextEditor.reveal(revealPosition.offset, revealPosition.length); + + IAnnotationModel annotationModel = getAnnotationModel(editor); + if (annotationModel == null) { + monitor.done(); + return; + } + + if (annotationModel instanceof IAnnotationModelExtension annotationModelExtension) { + + Map add = annotationsToAdd.parallelStream() + .collect(Collectors.toMap(e -> e.annotation, e -> e.position)); + Annotation[] remove = annotationsToRemove.toArray(s -> new Annotation[s]); + + annotationModelExtension.replaceAnnotations(remove, add); + } else { + for (Annotation annotation : annotationsToRemove) { + if (monitor.isCanceled()) + return; + annotationModel.removeAnnotation(annotation); + } + + for (AnnotationAndPosition annotation : annotationsToAdd) { + if (monitor.isCanceled()) + return; + annotationModel.addAnnotation(annotation.annotation, annotation.position); + } + } + }); + + } + + private static Position findFirstPosition(Collection annotations) { + if (annotations.isEmpty()) + return null; + + return annotations.parallelStream().min((a, b) -> a.position.offset - b.position.offset).map(e -> e.position) + .orElse(null); + } + + private static Collection getExistingAnnotations(IAnnotationModel annotationModel) { + Set result = new HashSet<>(); + Iterator annotationIter = annotationModel.getAnnotationIterator(); + + while (annotationIter.hasNext()) { + Annotation annotation = annotationIter.next(); + if (hasAnnotationType(annotation.getType())) + result.add(annotation); + } + + return result; + } + + private static IAnnotationModel getAnnotationModel(ITextEditor editor) { + if (editor == null) + return null; + + IEditorInput editorInput = editor.getEditorInput(); + IDocumentProvider documentProvider = editor.getDocumentProvider(); + if (editorInput != null && documentProvider != null) + return documentProvider.getAnnotationModel(editorInput); + + return null; + } + + private static boolean hasAnnotationType(String annotationType) { + return HelperTraceAnnotation.TRACE_MARKER_ID.equals(annotationType); + } + + protected static void addAnnotations(List annotations, Collection addThese, + String comment) { + for (EObject eObject : addThese) + addAnnotation(annotations, eObject, comment); + } + + protected static void addAnnotation(List annotations, EObject eObject, String comment) { + ICompositeNode textNode = NodeModelUtils.findActualNodeFor(eObject); + Annotation annotation = new Annotation(HelperTraceAnnotation.TRACE_MARKER_ID, false, comment); + Position position = new Position(textNode.getOffset(), textNode.getLength()); + annotations.add(new AnnotationAndPosition(annotation, position)); + } + + protected abstract List computeNewAnnotations(SubMonitor monitor, XtextEditor editor, + Collection elementIds); + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/CplexAnnotationHighlightStrategy.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/CplexAnnotationHighlightStrategy.java new file mode 100644 index 00000000..ed9f2630 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/CplexAnnotationHighlightStrategy.java @@ -0,0 +1,128 @@ +package org.emoflon.gips.debugger.connector.highlight; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.eclipse.xtext.ui.editor.model.IXtextDocument; +import org.eclipse.xtext.util.concurrent.IUnitOfWork; +import org.emoflon.gips.debugger.annotation.AnnotationAndPosition; +import org.emoflon.gips.debugger.api.ILPTraceKeywords; +import org.emoflon.gips.debugger.cplexLp.Variable; +import org.emoflon.gips.debugger.cplexLp.VariableDecleration; +import org.emoflon.gips.debugger.cplexLp.VariableRef; + +public class CplexAnnotationHighlightStrategy extends AnnotationBasedHighlightStrategy { + + @Override + protected List computeNewAnnotations(SubMonitor monitor, XtextEditor editor, + Collection elementIds) { + + IXtextDocument document = editor.getDocument(); + if (document == null) + return Collections.emptyList(); + + IUnitOfWork, XtextResource> work = resource -> { + + var workRemaining = elementIds.size(); + monitor.setWorkRemaining(workRemaining); + + List result = new ArrayList<>(elementIds.size() + 1); + + for (var elementId : elementIds) { + if (monitor.isCanceled()) { + monitor.done(); + break; + } + + ILPTraceKeywords.TypeValuePair typeAndValue = ILPTraceKeywords.getTypeAndValue(elementId); + + switch (typeAndValue.type()) { + case ILPTraceKeywords.TYPE_CONSTRAINT: { + var eObjects = getConstraintsWhichStartWith(resource, typeAndValue.value() + "_"); + addAnnotations(result, eObjects, null); + break; + } + case ILPTraceKeywords.TYPE_FUNCTION: { + String variables = elementIds.stream() // + .filter(e -> e.startsWith(ILPTraceKeywords.TYPE_FUNCTION_VAR)) // + .map(e -> e.substring(ILPTraceKeywords.TYPE_FUNCTION_VAR.length() + + ILPTraceKeywords.TYPE_VALUE_DELIMITER.length())) // + .collect(Collectors.joining(", ")); + var eObject = getGlobalObjective(resource); + addAnnotation(result, eObject, "Variables: " + variables); + break; + } + case ILPTraceKeywords.TYPE_OBJECTIVE: { + var eObject = getGlobalObjective(resource); + addAnnotation(result, eObject, null); + break; + } +// case ILPTraceKeywords.TYPE_MAPPING: { +// var eObjects = getMappings(resource, typeAndValue.value()); +// addAnnotations(result, eObjects, "Created by: " + typeAndValue.value()); +// break; +// } + default: { + var eObjects = getMappings(resource, typeAndValue.value()); + addAnnotations(result, eObjects, "Created by: " + typeAndValue.value()); + break; + } + } + + monitor.setWorkRemaining(--workRemaining); + } + + return result; + }; + + return document.readOnly(work); + } + + private Collection getConstraintsWhichStartWith(XtextResource resource, String constraintName) { + final var result = new LinkedList(); + + var model = (org.emoflon.gips.debugger.cplexLp.Model) resource.getContents().get(0); + for (var constraint : model.getConstraint().getStatements()) { + if (constraint.getName() != null && constraint.getName().startsWith(constraintName)) + result.add(constraint); + } + + return result; + } + + private EObject getGlobalObjective(XtextResource resource) { + var model = (org.emoflon.gips.debugger.cplexLp.Model) resource.getContents().get(0); + return model.getObjective(); + } + + private Collection getMappings(XtextResource resource, String name) { + final var result = new LinkedList(); + + var iterator = resource.getAllContents(); + while (iterator.hasNext()) { + var eObject = iterator.next(); + if (eObject instanceof Variable) { + if (eObject instanceof VariableDecleration vd) { + if (vd.getName().startsWith(name)) { + result.add(vd); + } + } else if (eObject instanceof VariableRef vr) { + if (vr.getRef().getName().startsWith(name)) { + result.add(vr); + } + } + } + } + + return result; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/CplexLpMarkerHighlightStrategy.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/CplexLpMarkerHighlightStrategy.java new file mode 100644 index 00000000..3448ef2d --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/CplexLpMarkerHighlightStrategy.java @@ -0,0 +1,114 @@ +package org.emoflon.gips.debugger.connector.highlight; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.emoflon.gips.debugger.annotation.AnnotationMarkerData; +import org.emoflon.gips.debugger.api.ILPTraceKeywords; +import org.emoflon.gips.debugger.cplexLp.Variable; +import org.emoflon.gips.debugger.cplexLp.VariableDecleration; +import org.emoflon.gips.debugger.cplexLp.VariableRef; + +public class CplexLpMarkerHighlightStrategy extends MarkerBasedHighlightStrategy { + + @Override + protected List computeMarker(XtextEditor editor, Collection localElementIds, + SubMonitor monitor) { + + List highlightMarkers = new ArrayList<>(); + + for (var localElement : localElementIds) { + +// var cache = traceMap.getTargets(localElement); +// if (!cache.isEmpty()) { +// highlightElements(cache); +// continue; +// } + + var typeAndValue = ILPTraceKeywords.getTypeAndValue(localElement); + + switch (typeAndValue.type()) { + case ILPTraceKeywords.TYPE_CONSTRAINT: { + var eObjects = getConstraintsWhichStartWith(editor, typeAndValue.value() + "_"); + var markers = convertEObjectsToMarkers(eObjects, "Created by: " + typeAndValue.value()); + highlightMarkers.addAll(markers); +// traceMap.mapOneToMany(localElement, eObjects); + break; + } + case ILPTraceKeywords.TYPE_FUNCTION: { + var variables = localElementIds.stream().filter(e -> e.startsWith(ILPTraceKeywords.TYPE_FUNCTION_VAR)) + .map(e -> e.substring(ILPTraceKeywords.TYPE_FUNCTION_VAR.length() + + ILPTraceKeywords.TYPE_VALUE_DELIMITER.length())); + var eObject = getGlobalObjective(editor); +// traceMap.map(localElement, eObject); + var marker = convertEObjectToMarker(eObject); + marker.comment = "Variables: " + variables.collect(Collectors.joining(", ")); + highlightMarkers.add(marker); + break; + } + case ILPTraceKeywords.TYPE_OBJECTIVE: { + var eObject = getGlobalObjective(editor); +// traceMap.map(localElement, eObject); + var marker = convertEObjectToMarker(eObject); + highlightMarkers.add(marker); + break; + } + case ILPTraceKeywords.TYPE_MAPPING: { + var eObjects = editor.getDocument().readOnly(resource -> { + var result = new LinkedList(); + var iterator = resource.getAllContents(); + while (iterator.hasNext()) { + var eObject = iterator.next(); + if (eObject instanceof Variable) { + if (eObject instanceof VariableDecleration vd) { + if (vd.getName().startsWith(typeAndValue.value())) { + result.add(vd); + } + } else if (eObject instanceof VariableRef vr) { + if (vr.getRef().getName().startsWith(typeAndValue.value())) { + result.add(vr); + } + } + } + } + return result; + }); + var markers = convertEObjectsToMarkers(eObjects, "Created by: " + typeAndValue.value()); + highlightMarkers.addAll(markers); + break; + } + } + } + + return highlightMarkers; + } + + protected static Collection getConstraintsWhichStartWith(XtextEditor editor, String constraintName) { + return editor.getDocument().readOnly(resource -> { + final var result = new LinkedList(); + + var model = (org.emoflon.gips.debugger.cplexLp.Model) resource.getContents().get(0); + for (var constraint : model.getConstraint().getStatements()) { + if (constraint.getName() != null && constraint.getName().startsWith(constraintName)) { + result.add(constraint); + } + } + + return result; + }); + } + + protected static EObject getGlobalObjective(XtextEditor editor) { + return editor.getDocument().readOnly(resource -> { + var model = (org.emoflon.gips.debugger.cplexLp.Model) resource.getContents().get(0); + return model.getObjective(); + }); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/MarkerBasedHighlightStrategy.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/MarkerBasedHighlightStrategy.java new file mode 100644 index 00000000..7cfba177 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/MarkerBasedHighlightStrategy.java @@ -0,0 +1,121 @@ +package org.emoflon.gips.debugger.connector.highlight; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.swt.widgets.Display; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.emoflon.gips.debugger.annotation.AnnotationMarkerData; +import org.emoflon.gips.debugger.annotation.HelperTraceAnnotation; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.connector.IHighlightStrategy; + +public class MarkerBasedHighlightStrategy implements IHighlightStrategy { + + @Override + public IStatus computeEditorHighlights(XtextEditor editor, ITraceContext context, String localModelId, + String remoteModelId, Collection remoteElementIds, SubMonitor monitor) { + + Collection localElementIds = context.resolveElementsByTrace(remoteModelId, localModelId, + remoteElementIds, true); + + removeEditorHighlights(editor, monitor); + + if (localElementIds.isEmpty()) + return Status.OK_STATUS; + + List markers = computeMarker(editor, localElementIds, monitor); + revealFirstMarker(editor, markers); + addHighlightMarkers(editor, markers); + + return Status.OK_STATUS; + } + + @Override + public IStatus removeEditorHighlights(XtextEditor editor, SubMonitor monitor) { + try { + HelperTraceAnnotation.deleteAllMarkers(editor.getResource()); + } catch (CoreException e) { + e.printStackTrace(); + return Status.error(e.getMessage()); + } + return Status.OK_STATUS; + } + + protected List computeMarker(XtextEditor editor, Collection localElementIds, + SubMonitor monitor) { + return editor.getDocument().readOnly(xtextResource -> { + List eObjects = convertUriFragmentsToEObjects(xtextResource, localElementIds); + return convertEObjectsToMarkers(eObjects); + }); + } + + public static void setHighlightElements(XtextEditor editor, Collection eObjects) { + List markers = convertEObjectsToMarkers(eObjects); + revealFirstMarker(editor, markers); + addHighlightMarkers(editor, markers); + } + + public static void addHighlightMarkers(XtextEditor editor, List markers) { + final IResource editorResource = editor.getResource(); + try { + for (final AnnotationMarkerData marker : markers) + HelperTraceAnnotation.createMarker(editorResource, marker.offset, marker.length, marker.comment); + } catch (CoreException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void revealFirstMarker(XtextEditor editor, List markers) { + if (!markers.isEmpty()) + revealMarker(editor, markers.get(0)); + } + + public static void revealMarker(XtextEditor editor, AnnotationMarkerData marker) { + Objects.requireNonNull(marker); + Display.getDefault().asyncExec(() -> { + editor.reveal(marker.offset, marker.length); + }); + } + + public static AnnotationMarkerData convertEObjectToMarker(EObject eObject) { + List markers = convertEObjectsToMarkers(Collections.singleton(eObject)); + return markers.get(0); + } + + public static List convertEObjectsToMarkers(Collection eObjects) { + return convertEObjectsToMarkers(eObjects, null); + } + + public static List convertEObjectsToMarkers(Collection eObjects, String comment) { + List markers = new ArrayList(eObjects.size()); + for (EObject eObject : eObjects) { + ICompositeNode textNode = NodeModelUtils.findActualNodeFor(eObject); + markers.add(new AnnotationMarkerData(textNode.getOffset(), textNode.getLength(), comment)); + } + return markers; + } + + public static List convertUriFragmentsToEObjects(XtextResource resource, Collection uriFragments) { + List eObjects = new ArrayList(uriFragments.size()); + for (String uriFragment : uriFragments) { + EObject eObject = resource.getEObject(uriFragment); + eObjects.add(eObject); + } + return eObjects; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/URIAnotationHighlightStrategy.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/URIAnotationHighlightStrategy.java new file mode 100644 index 00000000..7a63ffc9 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/connector/highlight/URIAnotationHighlightStrategy.java @@ -0,0 +1,40 @@ +package org.emoflon.gips.debugger.connector.highlight; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.eclipse.xtext.ui.editor.model.IXtextDocument; +import org.eclipse.xtext.util.concurrent.IUnitOfWork; +import org.emoflon.gips.debugger.annotation.AnnotationAndPosition; + +public class URIAnotationHighlightStrategy extends AnnotationBasedHighlightStrategy { + + @Override + protected List computeNewAnnotations(SubMonitor monitor, XtextEditor editor, + Collection elementIds) { + + IXtextDocument document = editor.getDocument(); + if (document == null) + return Collections.emptyList(); + + IUnitOfWork, XtextResource> work = resource -> { + List annotations = new ArrayList<>(elementIds.size()); + + for (String uriFragment : elementIds) { + EObject eObject = resource.getEObject(uriFragment); + addAnnotation(annotations, eObject, null); + } + + return annotations; + }; + + return document.readOnly(work); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/handler/OpenLpHandler.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/handler/OpenLpHandler.java new file mode 100644 index 00000000..32243075 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/handler/OpenLpHandler.java @@ -0,0 +1,41 @@ +package org.emoflon.gips.debugger.handler; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.ide.IDE; +import org.eclipse.xtext.ui.editor.utils.EditorUtils; +import org.emoflon.gips.debugger.utility.HelperGipsl; + +public class OpenLpHandler extends AbstractHandler { + +// @Inject +// EObjectAtOffsetHelper eObjectAtOffsetHelper; + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + + final var editor = EditorUtils.getActiveXtextEditor(event); + if (editor == null) { + return null; + } + + final var path = HelperGipsl.getLpPath(editor); + if (path == null) { + return null; + } + + final var file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); + + try { + IDE.openEditor(editor.getSite().getPage(), file, /* "org.emoflon.gips.debugger.CplexLp", */ true); + } catch (final PartInitException e1) { + e1.printStackTrace(); + } + + return null; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/handler/ToggleVisualisationHandler.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/handler/ToggleVisualisationHandler.java new file mode 100644 index 00000000..ea64a732 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/handler/ToggleVisualisationHandler.java @@ -0,0 +1,108 @@ +package org.emoflon.gips.debugger.handler; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.commands.IElementUpdater; +import org.eclipse.ui.menus.UIElement; +import org.emoflon.gips.debugger.TracePlugin; +import org.emoflon.gips.debugger.pref.PluginPreferences; + +public final class ToggleVisualisationHandler extends AbstractHandler implements IHandler, IElementUpdater { + + private final static String CMD_ID = "org.emoflon.gips.debugger.visualizer.toggle"; + + private final IPropertyChangeListener listener = this::onPreferenceChange; + + private ImageDescriptor iconOn; + private ImageDescriptor iconOff; + private ImageDescriptor iconDisabled; + + public ToggleVisualisationHandler() { +// loadImageDescriptors(); + setupPreferenceListener(); + } + + private void loadImageDescriptors() { + var assetsPath = new Path("assets"); + var bundle = TracePlugin.getInstance().getBundle(); + URL urlIconOn = FileLocator.find(bundle, assetsPath.append("toggle-visualisation-icon-on.on")); + URL urlIconOff = FileLocator.find(bundle, assetsPath.append("toggle-visualisation-icon-off.png")); +// URL urlIconDisabled = FileLocator.find(bundle, assetsPath.append("toggle-visualisation-icon-disabled.png")); + + this.iconOn = ImageDescriptor.createFromURL(urlIconOn); + this.iconOff = ImageDescriptor.createFromURL(urlIconOff); +// this.iconDisabled = ImageDescriptor.createFromURL(urlIconDisabled); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.ui/blob/master/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ToggleMarkOccurrencesAction.java + + private void setupPreferenceListener() { + PluginPreferences.getPreferenceStore().addPropertyChangeListener(listener); + } + + @Override + public void dispose() { + PluginPreferences.getPreferenceStore().removePropertyChangeListener(listener); + } + + private void onPreferenceChange(PropertyChangeEvent event) { + if (PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE.equals(event.getProperty())) + PlatformUI.getWorkbench().getService(ICommandService.class).refreshElements(CMD_ID, null); + } + + @Override + public void updateElement(UIElement element, Map parameters) { +// PlatformUI.getWorkbench().getService(IPreferencesService.class).getBoolean( +// PreferenceConstants.PREFERENCE_STORE_KEY, PreferenceConstants.PREF_TRACE_DISPLAY_ACTIVE, false, null); + var isActive = PluginPreferences.getPreferenceStore().getBoolean(PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE); + + // TODO: externalize + if (isActive) { + element.setTooltip("Pause GIPSL visualisation"); + element.setChecked(true); + // doesn't work for some reasons, icon turns invisible +// element.setIcon(iconOn); + } else { + element.setTooltip("Activate GIPSL visualisation"); + element.setChecked(false); +// element.setIcon(iconOff); + } + } + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { +// ITraceManager manager = TracePlugin.getInstance().getTraceManager(); // site.getService(ITraceManager.class); +// if (manager != null) +// manager.toggleVisualisation(); + +// var site = HandlerUtil.getActiveSiteChecked(event); +// site.getService(ICommandService.class).refreshElements(event.getCommand().getId(), null); + + var preferenceStore = PluginPreferences.getPreferenceStore(); + var isActive = preferenceStore.getBoolean(PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE); + preferenceStore.setValue(PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE, !isActive); + + try { + preferenceStore.save(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return null; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/PartSelectionFilterListener.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/PartSelectionFilterListener.java new file mode 100644 index 00000000..162e8c05 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/PartSelectionFilterListener.java @@ -0,0 +1,113 @@ +package org.emoflon.gips.debugger.listener; + +import java.util.Objects; +import java.util.function.Predicate; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.INullSelectionListener; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.SelectionListenerFactory.ISelectionModel; + +/** + * Inspired by {@link org.eclipse.ui.internal.PartSelectionListener} but without + * the reference leak in + * {@link org.eclipse.ui.internal.PartSelectionListener#addPartListener} + */ +public final class PartSelectionFilterListener implements ISelectionListener, INullSelectionListener { + + private final IWorkbenchPart part; + private final ISelectionListener delegate; + private final Predicate predicate; + + private IWorkbenchPart currentSelectionPart; + private ISelection currentSelection; + + private IWorkbenchPart lastSelectionPart; + private ISelection lastSelection; + + /** + * Constructs an intermediate selection listener which filters selections and + * lets them only through, if the predicate resolves to true. + * + * @param part the part this listener listens to, may not be null + * @param delegate the callback listener, may not be null + * @param predicate the predicate to test, may not be null + */ + public PartSelectionFilterListener(IWorkbenchPart part, ISelectionListener delegate, + Predicate predicate) { + this.part = Objects.requireNonNull(part, "part"); + this.delegate = Objects.requireNonNull(delegate, "delegate"); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + saveCurrentSelection(part, selection); + if (predicate.test(getModel())) { + delegateCall(part, selection); + } + } + + private void delegateCall(IWorkbenchPart part, ISelection selection) { + if (selection == null && (delegate instanceof INullSelectionListener)) { + delegate.selectionChanged(part, selection); + saveLastSelection(part, selection); + } else if (selection != null) { + delegate.selectionChanged(part, selection); + saveLastSelection(part, selection); + } + } + + private void saveCurrentSelection(IWorkbenchPart part, ISelection selection) { + currentSelectionPart = part; + currentSelection = selection; + } + + private void saveLastSelection(IWorkbenchPart part, ISelection selection) { + lastSelectionPart = part; + lastSelection = selection; + } + + private ISelectionModel getModel() { + return new ISelectionModel() { + + @Override + public IWorkbenchPart getTargetPart() { + return part; + } + + @Override + public IWorkbenchPart getLastDeliveredSelectionPart() { + return lastSelectionPart; + } + + @Override + public ISelection getLastDeliveredSelection() { + return lastSelection; + } + + @Override + public IWorkbenchPart getCurrentSelectionPart() { + return currentSelectionPart; + } + + @Override + public ISelection getCurrentSelection() { + return currentSelection; + } + + @Override + public boolean isTargetPartVisible() { + return part.getSite().getPage().isPartVisible(part); + } + + @Override + public boolean isSelectionPartVisible() { + return getCurrentSelectionPart() != null + && getCurrentSelectionPart().getSite().getPage().isPartVisible(getCurrentSelectionPart()); + } + }; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/WindowPartListener.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/WindowPartListener.java new file mode 100644 index 00000000..9785981a --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/WindowPartListener.java @@ -0,0 +1,67 @@ +package org.emoflon.gips.debugger.listener; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IWindowListener; +import org.eclipse.ui.IWorkbenchWindow; + +public final class WindowPartListener implements IWindowListener { + + private final Set windows = new HashSet<>(); + private final IPartListener2 listener; + + public WindowPartListener(IPartListener2 listener) { + this.listener = Objects.requireNonNull(listener, "listener"); + } + + public IPartListener2 getPartListener() { + return listener; + } + + public void removeAllPartListener() { + synchronized (windows) { + for (var window : windows) { + window.getPartService().removePartListener(listener); + } + this.windows.clear(); + } + } + + private void addListener(IWorkbenchWindow window) { + synchronized (windows) { + windows.add(window); + window.getPartService().addPartListener(this.listener); + } + } + + private void removeListener(IWorkbenchWindow window) { + synchronized (windows) { + windows.remove(window); + window.getPartService().removePartListener(this.listener); + } + } + + @Override + public void windowActivated(IWorkbenchWindow window) { + addListener(window); + } + + @Override + public void windowOpened(IWorkbenchWindow window) { + addListener(window); + } + + @Override + public void windowClosed(IWorkbenchWindow window) { + removeListener(window); + } + + @Override + public void windowDeactivated(IWorkbenchWindow window) { + + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/WorkbenchPartWatcher.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/WorkbenchPartWatcher.java new file mode 100644 index 00000000..1c1231eb --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/listener/WorkbenchPartWatcher.java @@ -0,0 +1,67 @@ +package org.emoflon.gips.debugger.listener; + +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +public final class WorkbenchPartWatcher { + + private final WindowPartListener listener; + + public WorkbenchPartWatcher(IPartListener2 delegate) { + this.listener = new WindowPartListener(delegate); + } + + /** + * Starts monitoring the {@link IWorkbench workbench} for any + * {@link IWorkbenchWindow windows} and adds the given part listener to them. + * This includes already existing windows and windows which where created after + * calling this method. + * + * + */ + public void start() { + stop(); + + var workbench = PlatformUI.getWorkbench(); + workbench.addWindowListener(listener); + + // call part listener on all opened parts + for (var window : workbench.getWorkbenchWindows()) { + listener.windowOpened(window); + + for (var page : window.getPages()) { + for (var reference : page.getEditorReferences()) { + listener.getPartListener().partOpened(reference); + } + + for (var reference : page.getViewReferences()) { + listener.getPartListener().partOpened(reference); + } + } + } + + var activeWindow = workbench.getActiveWorkbenchWindow(); + if (activeWindow != null) { + listener.windowActivated(activeWindow); + + var activePage = activeWindow.getActivePage(); + if (activePage != null) { + var activeRef = activePage.getActivePartReference(); + if (activeRef != null) { + listener.getPartListener().partActivated(activeRef); + } + } + } + } + + /** + * Stops monitoring and removes all listeners. + */ + public void stop() { + var workbench = PlatformUI.getWorkbench(); + workbench.removeWindowListener(listener); + listener.removeAllPartListener(); + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/menu/ContributionOpenLpFile.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/menu/ContributionOpenLpFile.java new file mode 100644 index 00000000..7cd79b8a --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/menu/ContributionOpenLpFile.java @@ -0,0 +1,68 @@ +package org.emoflon.gips.debugger.menu; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.jface.action.ContributionItem; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.CompoundContributionItem; +import org.eclipse.ui.ide.IDE; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.emoflon.gips.debugger.utility.HelperErrorDialog; +import org.emoflon.gips.debugger.utility.HelperGipsl; + +public class ContributionOpenLpFile extends CompoundContributionItem { + + public ContributionOpenLpFile() { + } + + public ContributionOpenLpFile(String id) { + super(id); + } + + @Override + protected IContributionItem[] getContributionItems() { + final var part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart(); + if (!(part instanceof final XtextEditor editor)) + return new IContributionItem[0]; + + final var targetPath = HelperGipsl.getLpPath(editor); + if (targetPath == null) + return new IContributionItem[0]; + + return new IContributionItem[] { new ContributionItem() { + @Override + public void fill(Menu menu, int index) { + final var item = new MenuItem(menu, SWT.CHECK, index); + item.setText("Open LP-File: " + targetPath.lastSegment()); + item.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent selectionEvent) { + final var targetFile = ResourcesPlugin.getWorkspace().getRoot().getFile(targetPath); + + try { + IDE.openEditor(part.getSite().getPage(), targetFile, "org.emoflon.gips.debugger.CplexLp", + true); + } catch (final PartInitException ex) { + ex.printStackTrace(); + var error = HelperErrorDialog.createMultiStatus(ex.getLocalizedMessage(), ex); + ErrorDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), + "Error", "Unable to open file: " + targetPath, error); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + } + } }; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferenceInitializer.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferenceInitializer.java new file mode 100644 index 00000000..452ede03 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferenceInitializer.java @@ -0,0 +1,29 @@ +package org.emoflon.gips.debugger.pref; + +import java.nio.file.Path; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.core.runtime.preferences.DefaultScope; + +public final class PluginPreferenceInitializer extends AbstractPreferenceInitializer { + + @Override + public void initializeDefaultPreferences() { + var node = DefaultScope.INSTANCE.getNode(PluginPreferences.STORE_KEY); + + // trace visualization is deactivated + node.putBoolean(PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE, false); + + // RMI Service + node.putBoolean(PluginPreferences.PREF_TRACE_RMI, true); + node.putInt(PluginPreferences.PREF_TRACE_RMI_PORT, 2842); + + // trace storage + node.putBoolean(PluginPreferences.PREF_TRACE_CACHE_ENABLED, true); + node.put(PluginPreferences.PREF_TRACE_CACHE_LOCATION, + IPath.fromPath(Path.of("trace")).makeRelative().toPortableString()); + node.putInt(PluginPreferences.PREF_TRACE_CACHE_MAX_TIME, 24); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferencePage.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferencePage.java new file mode 100644 index 00000000..6af8980f --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferencePage.java @@ -0,0 +1,81 @@ +package org.emoflon.gips.debugger.pref; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IntegerFieldEditor; +import org.eclipse.jface.preference.StringFieldEditor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Group; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.emoflon.gips.debugger.TracePlugin; + +/** + * GIPS Trace preference page + */ +public class PluginPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + public PluginPreferencePage() { + super(FieldEditorPreferencePage.GRID); + } + + // TODO: The layout can't be changed with FieldEditors, so to make it prettier + // or do anything more complex, the page needs to be reimplemented without + // FieldEditors. + // https://github.com/eclipse-jdt/eclipse.jdt.ui/blob/master/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/JavaBasePreferencePage.java + + @Override + public void init(IWorkbench workbench) { + setPreferenceStore(TracePlugin.getInstance().getPreferenceStore()); + setDescription("GIPS trace configuration"); + } + + @Override + protected void createFieldEditors() { + addField(new BooleanFieldEditor(PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE, "Trace &visualisation", + getFieldEditorParent())); + + { + var configGroup = new Group(getFieldEditorParent(), SWT.NULL); + configGroup.setLayout(new GridLayout()); + configGroup.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 2, 1)); + configGroup.setText("Trace RMI Service"); + + var editorRMI = new BooleanFieldEditor(PluginPreferences.PREF_TRACE_RMI, "&RMI enabled", configGroup); + addField(editorRMI); + + var editorRMIPort = new IntegerFieldEditor(PluginPreferences.PREF_TRACE_RMI_PORT, "RMI &port", configGroup); + editorRMIPort.setValidRange(1023, 65535); + addField(editorRMIPort); + } + + // horizontal line +// new Label(getFieldEditorParent(), SWT.SEPARATOR | SWT.HORIZONTAL) +// .setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); +// new Label(getFieldEditorParent(), SWT.NONE).setText("My Group Title"); + + { + var configGroup = new Group(getFieldEditorParent(), SWT.NULL); + configGroup.setLayout(new GridLayout()); + configGroup.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 2, 1)); + configGroup.setText("Trace storage"); + + var editorTraceStore = new BooleanFieldEditor(PluginPreferences.PREF_TRACE_CACHE_ENABLED, + "&Save/Load enabled", configGroup); + addField(editorTraceStore); + + var editorTraceStorePath = new StringFieldEditor(PluginPreferences.PREF_TRACE_CACHE_LOCATION, + "Project &relative path:", configGroup); + editorTraceStorePath.setEmptyStringAllowed(false); + editorTraceStorePath.setPropertyChangeListener(null); + addField(editorTraceStorePath); + +// var editorTraceStoreMaxTime = new IntegerFieldEditor(PluginPreferences.PREF_TRACE_CACHE_MAX_TIME, +// "Max &cache time (hours)", configGroup); +// editorTraceStoreMaxTime.setValidRange(0, Integer.MAX_VALUE); +// addField(editorTraceStoreMaxTime); + } + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferences.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferences.java new file mode 100644 index 00000000..d0c63ae6 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/pref/PluginPreferences.java @@ -0,0 +1,51 @@ +package org.emoflon.gips.debugger.pref; + +import org.eclipse.ui.preferences.ScopedPreferenceStore; +import org.emoflon.gips.debugger.TracePlugin; + +public final class PluginPreferences { + private PluginPreferences() { + + } + + public static final String STORE_KEY = TracePlugin.PLUGIN_ID; + + public static final String PREF_TRACE_DISPLAY_ACTIVE = "PREF_TRACE_DISPLAY_ACTIVE"; + public static final String PREF_TRACE_RMI = "PREF_TRACE_RMI"; + public static final String PREF_TRACE_RMI_PORT = "PREF_TRACE_RMI_PORT"; + + /** + * Enableds/Disables trace caching. + *

    + *
  • Type: {@link java.lang.Boolean} + *
  • Can not be null + *
+ */ + public static final String PREF_TRACE_CACHE_ENABLED = "PREF_TRACE_CACHE_ENABLED"; + + /** + * Trace cache location. The location is a relative path within any project. + *
    + *
  • Type: {@link java.lang.String} + *
  • Can not be null + *
  • Can not be empty + *
+ */ + public static final String PREF_TRACE_CACHE_LOCATION = "PREF_TRACE_CACHE_LOCATION"; + + /** + * To avoid unnecessary memory consumption, data that has not been used for x + * hours is removed from the cache. + *
    + *
  • Type: {@link java.lang.Integer} + *
  • Can not be null + *
  • Can not be less than 0 + *
+ */ + public static final String PREF_TRACE_CACHE_MAX_TIME = "PREF_TRACE_CACHE_MAX_TIME"; + + public static ScopedPreferenceStore getPreferenceStore() { + return TracePlugin.getInstance().getPreferenceStore(); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/EditorTracker.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/EditorTracker.java new file mode 100644 index 00000000..1ebe38e6 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/EditorTracker.java @@ -0,0 +1,96 @@ +package org.emoflon.gips.debugger.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IWorkbenchPartReference; +import org.emoflon.gips.debugger.api.IEditorTracker; +import org.emoflon.gips.debugger.connector.EditorTraceConnectionFactory; +import org.emoflon.gips.debugger.connector.IEditorTraceConnection; +import org.emoflon.gips.debugger.connector.IEditorTraceConnectionFactory; +import org.emoflon.gips.debugger.listener.WorkbenchPartWatcher; + +/** + * This class is used to monitor the workbench for any editor, which can be + * connected to the {@link TraceManager}. To do so it uses an + * {@link IEditorTraceConnectionFactory}. + * + * @see EditorTraceConnectionFactory + */ +final class EditorTracker implements IEditorTracker { + + private final IEditorTraceConnectionFactory editorConnectionFactory; + private final Map registeredEditors = new HashMap<>(); + private final WorkbenchPartWatcher partWatcher = new WorkbenchPartWatcher(new IPartListener2() { + @Override + public void partOpened(IWorkbenchPartReference partRef) { + if (partRef instanceof IEditorReference && partRef.getPart(false) instanceof IEditorPart editor) { + registerEditor(editor); + } + } + + @Override + public void partClosed(IWorkbenchPartReference partRef) { + if (partRef instanceof IEditorReference && partRef.getPart(false) instanceof IEditorPart editor) { + unregisterEditor(editor); + } + } + }); + + public EditorTracker(IEditorTraceConnectionFactory editorConnectionFactory) { + this.editorConnectionFactory = Objects.requireNonNull(editorConnectionFactory, "editorConnectionFactory"); + } + + public void initialize() { + partWatcher.start(); + } + + public void dispose() { + partWatcher.stop(); + unregisterAllEditors(); + } + + @Override + public boolean registerEditor(IEditorPart editor) { + Objects.requireNonNull(editor, "editor"); + + synchronized (registeredEditors) { + if (registeredEditors.containsKey(editor)) { + return true; + } + + if (editorConnectionFactory.canConnect(editor)) { + var connection = editorConnectionFactory.createConnection(editor); + connection.connect(); + registeredEditors.put(editor, connection); + return true; + } + } + + return false; + } + + @Override + public void unregisterEditor(IEditorPart editor) { + synchronized (registeredEditors) { + var connection = registeredEditors.get(editor); + if (connection != null) { + connection.disconnect(); + } + } + } + + public void unregisterAllEditors() { + synchronized (registeredEditors) { + for (var connection : registeredEditors.values()) { + connection.disconnect(); + } + registeredEditors.clear(); + } + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/ProjectTraceContext.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/ProjectTraceContext.java new file mode 100644 index 00000000..cd95da32 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/ProjectTraceContext.java @@ -0,0 +1,365 @@ +package org.emoflon.gips.debugger.service; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.jface.preference.IPreferenceStore; +import org.emoflon.gips.debugger.TracePlugin; +import org.emoflon.gips.debugger.api.IModelLink; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.api.TraceModelNotFoundException; +import org.emoflon.gips.debugger.api.event.ITraceSelectionListener; +import org.emoflon.gips.debugger.api.event.ITraceUpdateListener; +import org.emoflon.gips.debugger.api.event.TraceSelectionEvent; +import org.emoflon.gips.debugger.api.event.TraceUpdateEvent; +import org.emoflon.gips.debugger.pref.PluginPreferences; +import org.emoflon.gips.debugger.trace.EcoreReader; +import org.emoflon.gips.debugger.trace.ModelReference; +import org.emoflon.gips.debugger.trace.PathFinder.SearchDirection; +import org.emoflon.gips.debugger.trace.Root; +import org.emoflon.gips.debugger.trace.TraceGraph; +import org.emoflon.gips.debugger.trace.TraceModelLink; +import org.emoflon.gips.debugger.trace.TransformEcore2Graph; +import org.emoflon.gips.debugger.utility.HelperEclipse; + +public final class ProjectTraceContext implements ITraceContext { + + public static final String CACHE_FILE_NAME = "trace_cache.bin"; + + public static boolean isCacheEnabled(IPreferenceStore store) { + return store.getBoolean(PluginPreferences.PREF_TRACE_CACHE_ENABLED); + } + + private static IFolder getCacheLocation(IPreferenceStore store, IProject project) { + var config = TracePlugin.getInstance().getPreferenceStore(); + var location = config.getString(PluginPreferences.PREF_TRACE_CACHE_LOCATION); + IPath relativePath = IPath.fromPortableString(location); + if (relativePath.isAbsolute()) + throw new IllegalArgumentException("Cache location is not relative"); + + return project.getFolder(relativePath); + } + + private static IFile getCacheFile(IPreferenceStore store, IProject project) { + IFolder cacheFolder = getCacheLocation(store, project); + return cacheFolder.getFile(ProjectTraceContext.CACHE_FILE_NAME); + } + + public static boolean hasCache(IPreferenceStore store, IProject project) { + IFile cacheFile = getCacheFile(store, project); // .exists() check is not reliable because it just returns a + // cached value by eclipse + + Path osPath = cacheFile.getLocation().toPath(); + return Files.exists(osPath); // we need to ask the OS directly + } + + private final Object syncLock = new Object(); + private final TraceManager service; + private final String contextId; + private TraceGraph graph = new TraceGraph(); + + private final ListenerList selectionListener = new ListenerList<>(); + private final ListenerList updateListener = new ListenerList<>(); + + private boolean graphDirty = false; + + public ProjectTraceContext(TraceManager service, String contextId) { + this.service = Objects.requireNonNull(service, "service"); + this.contextId = Objects.requireNonNull(contextId, "contextId"); + } + + /** + * Returns the {@link IProject} associated with this context. The name of the + * project is the same as the {@link #getContextId() id} of this context. The + * project may be null or inaccessible. + * + * @return the project or null + */ + public IProject getAssociatedProject() { + return HelperEclipse.tryAndGetProject(contextId); + } + +// public void initialize() { +// +// } +// +// public void dispose() { +// +// } + + /** + * + * @param createParentFolders + * @return a path to the cache file or null if this context does not have one + * @throws CoreException if folder creation failed + */ + private Path tryAndgetCacheLocation(boolean createParentFolders) throws CoreException { + IProject project = getAssociatedProject(); + if (project == null || !project.isAccessible()) + return null; + + IFolder cacheFolder = getCacheLocation(PluginPreferences.getPreferenceStore(), project); + if (createParentFolders && !cacheFolder.exists()) + cacheFolder.create(true, true, null); + + Path cacheFilePath = cacheFolder.getFile(CACHE_FILE_NAME).getLocation().toPath(); + + return cacheFilePath; + } + + public void saveCache() throws CoreException { + synchronized (syncLock) { + if (!graphDirty) + return; + + Path cacheFilePath = tryAndgetCacheLocation(true); + if (cacheFilePath == null) + return; + + try (var outputStream = Files.newOutputStream(cacheFilePath, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); + var objectOut = new ObjectOutputStream(outputStream)) { + + objectOut.writeObject(graph); + graphDirty = false; + + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void deleteCache() { + try { + Path cacheFilePath = tryAndgetCacheLocation(false); + if (cacheFilePath != null) + Files.delete(cacheFilePath); + } catch (IOException | CoreException e) { + e.printStackTrace(); + } + } + + public void loadCacheIfAvailable() { + synchronized (syncLock) { + + Path cacheFilePath = null; + try { + cacheFilePath = tryAndgetCacheLocation(false); + } catch (CoreException e) { + e.printStackTrace(); + } + + if (cacheFilePath == null || !Files.exists(cacheFilePath)) + return; + + try (var inputStream = Files.newInputStream(cacheFilePath, StandardOpenOption.READ); + var objectIn = new ObjectInputStream(inputStream)) { + + this.graph = (TraceGraph) objectIn.readObject(); + this.graphDirty = false; + } catch (NoSuchFileException e) { + // ignore + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + + @Override + public Set getAllModels() { + return new HashSet<>(graph.getAllModelReferenceIds()); + } + + @Override + public void addListener(ITraceSelectionListener listener) { + Objects.requireNonNull(listener, "listener"); + selectionListener.add(listener); + } + + @Override + public void removeListener(ITraceSelectionListener listener) { + selectionListener.remove(listener); + } + + @Override + public void addListener(ITraceUpdateListener listener) { + Objects.requireNonNull(listener, "listener"); + updateListener.add(listener); + } + + @Override + public void removeListener(ITraceUpdateListener listener) { + updateListener.remove(listener); + } + + @Override + public void selectElementsByTrace(String modelId, Collection elementIds) + throws TraceModelNotFoundException { + Objects.requireNonNull(modelId, "modelId"); + Objects.requireNonNull(elementIds, "elementIds"); + + if (!graph.hasModelReference(modelId)) + throw new TraceModelNotFoundException(modelId); + + if (service.isVisualisationActive()) + fireModelSelectionNotification(modelId, elementIds); + + } + + @Override + public TraceManager getTraceManager() { + return this.service; + } + + @Override + public String getContextId() { + return contextId; + } + + @Override + public boolean hasTraceFor(String modelId) { + return graph.hasModelReference(modelId); + } + + public void deleteModel(String modelId) { + Collection fromIds = graph.getSourceModelIds(modelId); + Collection toIds = graph.getTargetModelIds(modelId); + if (graph.removeModelReference(modelId)) { + Collection updated = new HashSet<>(); + updated.add(modelId); + updated.addAll(fromIds); + updated.addAll(toIds); + fireModelUpdateNotification(updated); + } + } + + public TraceModelLink getModelLink(String srcModel, String dstModel) { + return graph.getLink(srcModel, dstModel); + } + + public void deleteAllModels() { + Collection allIds = getAllModels(); + graph.clear(); + fireModelUpdateNotification(allIds); + } + + public void deleteModelLink(String srcModel, String dstModel) { + if (graph.removeTraceLink(srcModel, dstModel)) + fireModelUpdateNotification(Set.of(srcModel, dstModel)); + } + + @Override + public Set getSourceModels(String modelId) { + return graph.getSourceModelIds(modelId); + } + + @Override + public Set getTargetModels(String modelId) { + return graph.getTargetModelIds(modelId); + } + + @Override + public IModelLink getModelChain(String startModelId, String endModelId) { + var chain = graph.buildTraceChain(startModelId, endModelId, SearchDirection.AllDirections); + // TODO: some caching. + // store chain between 1 and 2 + // recalculate iff transformation chain between 1 and 2 changes (updateTrace) + // luckily, the graph is tiny. + + return new IModelLink() { + @Override + public String getStartModelId() { + return startModelId; + } + + @Override + public String getEndModelId() { + return endModelId; + } + + @Override + public boolean isResolved() { + return chain.isResolved(); + } + + @Override + public Collection resolveElementsFromSrcToDst(Collection selectedElementsById) { + return chain.resolveElementFromStartToEnd(selectedElementsById); + } + }; + } + + @Override + public Collection resolveElementsByTrace(String startModelId, String endModelId, + Collection elements, boolean bidirectional) { + var link = getModelChain(startModelId, endModelId); + if (link.isResolved()) + return link.resolveElementsFromSrcToDst(elements); + + return Collections.emptyList(); + } + + @Override + public void updateTraceModel(TraceModelLink traceLink) { + graph.addOrReplaceTraceLink(traceLink); + + var updatedModels = Set.of(traceLink.getSourceModel(), traceLink.getTargetModel()); + fireModelUpdateNotification(updatedModels); + } + + @Override + public void loadAndUpdateTraceModel(URI fileURI) { + var reader = new EcoreReader(fileURI); + if (reader.doesFileExist()) { + Root model = reader.loadModel(); + TransformEcore2Graph.addModelToGraph(model, graph); + + var updatedModels = model.getModels().stream().map(ModelReference::getModelId).collect(Collectors.toSet()); + fireModelUpdateNotification(updatedModels); + } + } + + private void fireModelSelectionNotification(String modelId, Collection elementIds) { + Objects.requireNonNull(modelId, "modelId"); + Objects.requireNonNull(elementIds, "elementIds"); + + // TODO: run (each) in a separate thread with a cancellation token + + var event = new TraceSelectionEvent(this, modelId, elementIds); + for (var listener : selectionListener) + listener.selectedByModel(event); + } + + /** + * TODO: Should only be fired if something has really changed + * + * @param updatedModels + */ + private void fireModelUpdateNotification(Collection updatedModels) { + Objects.requireNonNull(updatedModels, "updatedModels"); + graphDirty = true; + + var event = new TraceUpdateEvent(this, updatedModels); + for (var listener : this.updateListener) + listener.updatedModels(event); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/TraceManager.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/TraceManager.java new file mode 100644 index 00000000..9aca5a0e --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/TraceManager.java @@ -0,0 +1,267 @@ +package org.emoflon.gips.debugger.service; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.emoflon.gips.debugger.api.IEditorTracker; +import org.emoflon.gips.debugger.api.ITraceManager; +import org.emoflon.gips.debugger.api.TraceModelNotFoundException; +import org.emoflon.gips.debugger.api.event.ITraceManagerListener; +import org.emoflon.gips.debugger.api.event.ITraceSelectionListener; +import org.emoflon.gips.debugger.api.event.ITraceUpdateListener; +import org.emoflon.gips.debugger.api.event.TraceManagerEvent; +import org.emoflon.gips.debugger.api.event.TraceManagerEvent.EventType; +import org.emoflon.gips.debugger.connector.CplexLpEditorTraceConnectionFactory; +import org.emoflon.gips.debugger.connector.EditorTraceConnectionFactory; +import org.emoflon.gips.debugger.connector.GenericXmiEditorTraceConnectionFactory; +import org.emoflon.gips.debugger.connector.GipslEditorTraceConnectionFactory; +import org.emoflon.gips.debugger.pref.PluginPreferences; +import org.emoflon.gips.debugger.utility.HelperEclipse; + +public class TraceManager implements ITraceManager { + + private final Object syncLock = new Object(); + private final IResourceChangeListener workspaceResourceListener = this::onWorkspaceResourceChange; + private final IPropertyChangeListener preferenceListener = this::onPreferenceChange; + + private final ListenerList contextListener = new ListenerList<>(); + + private final Map contextById = new HashMap<>(); + + private EditorTracker tracker; + private boolean visualisationActive; + + public void initialize() { + synchronized (syncLock) { + contextById.clear(); + + EditorTraceConnectionFactory connectionFactory = new EditorTraceConnectionFactory(); + connectionFactory.addConnectionFactory(new GipslEditorTraceConnectionFactory()); + connectionFactory.addConnectionFactory(new CplexLpEditorTraceConnectionFactory()); + connectionFactory.addConnectionFactory(new GenericXmiEditorTraceConnectionFactory()); + + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + workspace.addResourceChangeListener(workspaceResourceListener); + + IPreferenceStore preferences = PluginPreferences.getPreferenceStore(); + preferences.addPropertyChangeListener(preferenceListener); + visualisationActive = preferences.getBoolean(PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE); + + // Restore any previously saved context + if (ProjectTraceContext.isCacheEnabled(preferences)) { + IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (var project : allProjects) { + if (ProjectTraceContext.hasCache(preferences, project)) + getOrCreateContext(project.getName(), true); + } + } + + tracker = new EditorTracker(connectionFactory); + tracker.initialize(); + } + } + + public void dispose() { + synchronized (syncLock) { + IPreferenceStore preferences = PluginPreferences.getPreferenceStore(); + preferences.removePropertyChangeListener(preferenceListener); + + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + workspace.removeResourceChangeListener(workspaceResourceListener); + + tracker.dispose(); + tracker = null; + + if (ProjectTraceContext.isCacheEnabled(preferences)) { + for (var context : contextById.values()) { + try { + context.saveCache(); + } catch (CoreException e) { + e.printStackTrace(); + } +// context.dispose(); + } + } + + contextById.clear(); + } + } + + private void onPreferenceChange(PropertyChangeEvent event) { + if (PluginPreferences.PREF_TRACE_DISPLAY_ACTIVE.equals(event.getProperty())) + visualisationActive = ((Boolean) event.getNewValue()).booleanValue(); + } + + @Override + public void addTraceManagerListener(ITraceManagerListener listener) { + contextListener.add(Objects.requireNonNull(listener, "listener")); + } + + @Override + public void removeTraceManagerListener(ITraceManagerListener listener) { + contextListener.remove(listener); + } + + @Override + public boolean isVisualisationActive() { + return visualisationActive; + } + + @Override + public void addListener(String contextId, ITraceSelectionListener listener) { + var context = getOrCreateContext(contextId, true); + context.addListener(listener); + } + + @Override + public void removeListener(ITraceSelectionListener listener) { + synchronized (syncLock) { + for (var context : contextById.values()) + context.removeListener(listener); + } + } + + @Override + public void removeListener(String contextId, ITraceSelectionListener listener) { + var context = getOrCreateContext(contextId, false); + if (context != null) + context.addListener(listener); + } + + @Override + public void addListener(String contextId, ITraceUpdateListener listener) { + var context = getOrCreateContext(contextId, true); + context.addListener(listener); + } + + @Override + public void removeListener(ITraceUpdateListener listener) { + synchronized (syncLock) { + for (var context : contextById.values()) + context.removeListener(listener); + } + } + + @Override + public void removeListener(String contextId, ITraceUpdateListener listener) { + var context = getOrCreateContext(contextId, false); + if (context != null) + context.addListener(listener); + } + + @Override + public IEditorTracker getEditorTracker() { + return tracker; + } + + @Override + public ProjectTraceContext getContext(String contextId) { + return getOrCreateContext(contextId, true); + } + + @Override + public boolean doesContextExist(String contextId) { + return contextById.containsKey(contextId); + } + + @Override + public void selectElementsByTraceModel(String contextId, String modelId, Collection selection) + throws TraceModelNotFoundException { + var context = getOrCreateContext(contextId, false); + if (context != null) + context.selectElementsByTrace(modelId, selection); + } + + @Override + public Set getAvailableContextIds() { + return new HashSet<>(contextById.keySet()); + } + + private void onWorkspaceResourceChange(IResourceChangeEvent event) { + if (event == null || event.getDelta() == null) + return; + + try { + event.getDelta().accept(delta -> { + var resource = delta.getResource(); + if (resource.getType() == IResource.PROJECT && // + delta.getKind() == IResourceDelta.CHANGED && // + isFlagSet(delta.getFlags(), IResourceDelta.OPEN)) { + + onWorkspaceProjectChange(resource.getProject()); + } + return true; + }); + } catch (CoreException e) { + e.printStackTrace(); + } + } + + private static boolean isFlagSet(int flags, int isSet) { + return (flags & isSet) != 0; + } + + private void onWorkspaceProjectChange(IProject project) { + if (!project.isAccessible()) + removeContext(project.getName()); + } + + private ProjectTraceContext getOrCreateContext(String contextId, boolean createOnDemand) { + Objects.requireNonNull(contextId, "contextId"); + ProjectTraceContext context = contextById.get(contextId); + + if (createOnDemand && context == null) { + synchronized (syncLock) { + context = contextById.get(contextId); + if (context != null) + return context; + + var project = HelperEclipse.tryAndGetProject(contextId); + if (project == null) + throw new IllegalArgumentException("Unknown project for context id: " + contextId); + + context = new ProjectTraceContext(this, contextId); + contextById.put(contextId, context); + + if (ProjectTraceContext.isCacheEnabled(PluginPreferences.getPreferenceStore())) + context.loadCacheIfAvailable(); + } + + var event = new TraceManagerEvent(this, EventType.NEW, contextId); + for (var listener : contextListener) + listener.contextChanged(event); + } + + return context; + } + + private void removeContext(String contextId) { + synchronized (syncLock) { + var context = contextById.remove(contextId); + if (context != null) { +// context.dispose(); + + var event = new TraceManagerEvent(this, EventType.DELETED, contextId); + for (var listener : contextListener) + listener.contextChanged(event); + } + } + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/TraceRemoteService.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/TraceRemoteService.java new file mode 100644 index 00000000..a2af1bfe --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/service/TraceRemoteService.java @@ -0,0 +1,137 @@ +package org.emoflon.gips.debugger.service; + +import java.rmi.AccessException; +import java.rmi.AlreadyBoundException; +import java.rmi.NoSuchObjectException; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.UnicastRemoteObject; + +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.ui.preferences.ScopedPreferenceStore; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.api.ITraceManager; +import org.emoflon.gips.debugger.api.ITraceRemoteService; +import org.emoflon.gips.debugger.pref.PluginPreferences; +import org.emoflon.gips.debugger.trace.TraceModelLink; + +public final class TraceRemoteService implements ITraceRemoteService { + + private final IPropertyChangeListener preferenceListener = this::onPreferenceChange; + private boolean isRunning = false; + private Registry registry; + private ITraceRemoteService exportedStub; + private int port; + + public TraceRemoteService() throws RemoteException { + super(); + } + + public int getPort() { + return port; + } + + synchronized private void setPort(int port) { + if (port < 0) + throw new IllegalArgumentException("port must be greater than or equal to 0"); + if (isRunning) + throw new UnsupportedOperationException("Unable to change port while service is running"); + + this.port = port; + } + + synchronized public void initialize() { + ScopedPreferenceStore preferences = PluginPreferences.getPreferenceStore(); + preferences.addPropertyChangeListener(preferenceListener); + + setPort(preferences.getInt(PluginPreferences.PREF_TRACE_RMI_PORT)); + + boolean startSerivce = preferences.getBoolean(PluginPreferences.PREF_TRACE_RMI); + if (startSerivce) + startService(); + } + + synchronized public void dispose() { + PluginPreferences.getPreferenceStore().removePropertyChangeListener(preferenceListener); + stopService(); + } + + synchronized private void startService() { + if (isRunning) + return; // nothing to do here + + try { // setup RMI service + exportedStub = (ITraceRemoteService) UnicastRemoteObject.exportObject(this, 0); + registry = LocateRegistry.createRegistry(port); + registry.bind(ITraceRemoteService.SERVICE_NAME, exportedStub); + } catch (AccessException e) { + throw new IllegalStateException("Unable to start service. Access denied", e); + } catch (AlreadyBoundException e) { + throw new IllegalStateException("An instance of this service is already running", e); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + + isRunning = true; + } + + synchronized private void stopService() { + if (!isRunning) + return; // nothing to do here + + try { + UnicastRemoteObject.unexportObject(exportedStub, true); + exportedStub = null; + } catch (NoSuchObjectException e) { + // no such object? No problem! + } + + try { + registry.unbind(ITraceRemoteService.SERVICE_NAME); + registry = null; + } catch (AccessException e) { + throw new IllegalStateException("Unable to shutdown service. Access denied", e); + } catch (NotBoundException e) { + // service already unbound? Strange but okay. + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + + isRunning = false; + } + + private void onPreferenceChange(PropertyChangeEvent event) { + switch (event.getProperty()) { + case PluginPreferences.PREF_TRACE_RMI: + boolean shouldServiceBeRunning = ((Boolean) event.getNewValue()).booleanValue(); + if (this.isRunning == shouldServiceBeRunning) + return; + + if (shouldServiceBeRunning) + startService(); + else + stopService(); + + break; + case PluginPreferences.PREF_TRACE_RMI_PORT: + int newPort = ((Number) event.getNewValue()).intValue(); + if (this.port == newPort) + return; + + stopService(); + setPort(newPort); + startService(); + break; + } + } + + @Override + public void updateTraceModel(String contextId, TraceModelLink traceLink) { + ITraceContext context = ITraceManager.getInstance().getContext(contextId); + context.updateTraceModel(traceLink); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEObjects.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEObjects.java new file mode 100644 index 00000000..1b4a1c51 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEObjects.java @@ -0,0 +1,27 @@ +package org.emoflon.gips.debugger.utility; + +import org.eclipse.emf.ecore.EObject; + +public final class HelperEObjects { + private HelperEObjects() { + + } + + public static boolean hasParentOfType(EObject child, Class... parentTypes) { + return getParentOfType(child, parentTypes) != null; + } + + public static EObject getParentOfType(EObject child, Class... parentTypes) { + var target = child; + while (target != null) { + for (var type : parentTypes) { + if (type.isInstance(target)) { + return target; + } + } + + target = target.eContainer(); + } + return null; + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEclipse.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEclipse.java new file mode 100644 index 00000000..d6cbb3de --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEclipse.java @@ -0,0 +1,365 @@ +package org.emoflon.gips.debugger.utility; + +import java.util.concurrent.Callable; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.emf.common.CommonPlugin; +import org.eclipse.emf.common.ui.URIEditorInput; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.presentation.EcoreEditor; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.xtext.ui.editor.XtextEditor; + +public final class HelperEclipse { + private HelperEclipse() { + + } + + public static IEclipseContext getActiveContext() { + final IEclipseContext context = getWorkbenchContext(); + return context == null ? null : context.getActiveLeaf(); + } + + public static IEclipseContext getWorkbenchContext() { + return PlatformUI.getWorkbench().getService(IEclipseContext.class); + } + + public static IWorkspace getWorkspace() { + return ResourcesPlugin.getWorkspace(); + } + + public static org.eclipse.emf.common.util.URI toPlatformURI(org.eclipse.emf.common.util.URI uri) { + if (uri.isPlatform()) { + return uri; + } + + var path = IPath.fromOSString(uri.toFileString()); + var project = tryAndGetProject(path); + if (project == null) { + throw new IllegalArgumentException("Unable to locate project for URI: " + uri); + } + + var relativePath = path.makeRelativeTo(project.getLocation()); + var newUri = URI.createPlatformResourceURI("/" + project.getName() + "/" + relativePath.toString(), true); + + if (uri.hasFragment()) { + newUri.appendFragment(uri.fragment()); + } + if (uri.hasQuery()) { + newUri.appendQuery(uri.query()); + } + + return newUri; + } + + public static org.eclipse.emf.common.util.URI toFileURI(org.eclipse.emf.common.util.URI uri) { + if (uri.isFile()) { + return uri; + } + + var newUri = CommonPlugin.resolve(uri); + if (uri.hasFragment()) { + newUri.appendFragment(uri.fragment()); + } + if (uri.hasQuery()) { + newUri.appendQuery(uri.query()); + } + + return newUri; + } + + public static boolean isValidPlatformURI(org.eclipse.emf.common.util.URI uri) { + if (!uri.isPlatform()) { + return false; + } + + var projectName = uri.segment(1); + for (var project : getWorkspace().getRoot().getProjects()) { + if (project.getName().equals(projectName)) { + return true; + } + } + return false; + } + + public static IProject tryAndGetProject(String name) { + for (var project : getWorkspace().getRoot().getProjects()) { + if (project.getName().equalsIgnoreCase(name)) { + return project; + } + } + return null; + } + + public static IProject tryAndGetProject(org.eclipse.emf.ecore.resource.Resource resource) { + var uri = resource.getURI(); + if (uri == null) { + throw new IllegalArgumentException("Resource has no URI"); + } + return tryAndGetProject(uri); + } + + public static IProject tryAndGetProject(org.eclipse.emf.common.util.URI uri) { + if (uri.isPlatformResource()) { + String expectedProjectName = uri.segment(1); + IProject project = tryAndGetProject(expectedProjectName); + if (project != null) + return project; + } + + var path = IPath.fromOSString(uri.toFileString()); + return tryAndGetProject(path); + } + + public static IProject tryAndGetProject(org.eclipse.core.resources.IResource resource) { + return resource.getProject(); + } + + public static IProject tryAndGetProject(org.eclipse.core.resources.IFile file) { + return file.getProject(); + } + + public static IProject tryAndGetProject(org.eclipse.core.resources.IFolder folder) { + return folder.getProject(); + } + + public static IProject tryAndGetProject(org.eclipse.core.runtime.IPath path) { + if (path.isAbsolute()) { + for (var project : getWorkspace().getRoot().getProjects()) { + if (project.getLocation().isPrefixOf(path)) { + return project; + } + } + + return null; + } + + // search for partial project location match at the start of path +// for (var project : getWorkspace().getRoot().getProjects()) { +// var projectLocation = project.getLocation(); +// +// var upperSearchLimit = Math.min(path.segmentCount(), projectLocation.segmentCount()); +// for (var i = 0; i < upperSearchLimit; ++i) { +// if (projectLocation.lastSegment().equals(path.segment(i))) { +// +// var counter = i - 1; +// for (; counter >= 0; --counter) { +// if (!projectLocation.segment(counter).equals(path.segment(counter))) { +// break; +// } +// } +// +// if (counter < 0) { +// return project; +// } +// } +// } +// } + + for (var project : getWorkspace().getRoot().getProjects()) { + if (project.getLocation().lastSegment().equals(path.segment(0))) { + return project; + } + } + + return null; + } + + @Deprecated + public static IProject tryAndGetProject(org.eclipse.ui.IEditorInput input) { + IProject project = input.getAdapter(IProject.class); + if (project != null) { + return project; + } + + { + var resource = input.getAdapter(IResource.class); + if (resource != null) { + project = tryAndGetProject(resource); + if (project != null) { + return project; + } + } + } + + { + var file = input.getAdapter(IFile.class); + if (file != null) { + project = tryAndGetProject(file); + if (project != null) { + return project; + } + } + } + + if (input instanceof URIEditorInput uriInput) { + project = tryAndGetProject(uriInput.getURI()); + if (project != null) { + return project; + } + } + + return null; + } + + @Deprecated + public static IProject getProjectOfResource(final IWorkspace workspace, + final org.eclipse.emf.ecore.resource.Resource resource) { + + if (resource.getURI().segmentCount() < 2) { + return null; + } + + var expectedProjectName = resource.getURI().segment(1); + for (var project : workspace.getRoot().getProjects()) { + if (project.getName().equalsIgnoreCase(expectedProjectName)) { + return project; + } + } + + return null; + } + + @Deprecated + public static IProject tryAndFindProject(final IEditorInput input) { + IProject project = input.getAdapter(IProject.class); + if (project != null) { + return project; + } + + { + var resource = input.getAdapter(IResource.class); + if (resource != null) { + project = resource.getProject(); + } + if (project != null) { + return project; + } + } + + { + var file = input.getAdapter(IFile.class); + if (file != null) { + project = file.getProject(); + } + if (project != null) { + return project; + } + } + + { + if (input instanceof URIEditorInput uriInput) { + project = tryAndFindProject(uriInput.getURI()); + if (project != null) { + return project; + } + } + } + + return null; + } + + @Deprecated + public static IProject tryAndFindProject(final URI uri) { + if (uri.isPlatformResource()) { + var expectedProjectName = uri.segment(1); + for (var project : getWorkspace().getRoot().getProjects()) { + if (project.getName().equalsIgnoreCase(expectedProjectName)) { + return project; + } + } + + return null; + } + + var filePath = IPath.fromOSString(uri.toFileString()); + for (var project : getWorkspace().getRoot().getProjects()) { + var matches = project.getLocation().matchingFirstSegments(filePath); + if (matches == project.getLocation().segmentCount()) { + return project; + } + } + + return null; + } + + @Deprecated + public static XtextEditor findActiveXtextEditor(final IFile file) { + var activeEditors = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getEditorReferences(); + + for (var activeEditor : activeEditors) { + var part = activeEditor.getPart(false); + if (!(part instanceof XtextEditor eEditor)) { + continue; + } + + var input = eEditor.getEditorInput(); + if (input instanceof FileEditorInput fileInput) { + if (file.equals(fileInput.getFile())) { + return eEditor; + } + } + } + + return null; + } + + @Deprecated + public static T runInUIThread(final Callable call) throws Exception { + var result = PlatformUI.getWorkbench().getDisplay().syncCall(() -> call.call()); + return result; + } + + @Deprecated + public static EcoreEditor findActiveEcoreEditor(final URI uri) { + var activeEditors = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getEditorReferences(); + + if (uri.isPlatform()) { + var relativePath = new Path(uri.toPlatformString(true)).makeRelative(); + + for (var activeEditor : activeEditors) { + var part = activeEditor.getPart(false); + if (!(part instanceof EcoreEditor eEditor)) { + continue; + } + + var input = eEditor.getEditorInput(); + + var resource = input.getAdapter(IResource.class); + if (resource != null) { + var editorFile = resource.getProjectRelativePath(); + if (editorFile.segmentCount() == relativePath.segmentCount()) { + if (editorFile.matchingFirstSegments(editorFile) == editorFile.segmentCount()) { + return eEditor; + } + } + } + } + } else if (uri.isFile()) { + var editorInput = new URIEditorInput(uri); + + for (var activeEditor : activeEditors) { + var part = activeEditor.getPart(false); + if (!(part instanceof EcoreEditor eEditor)) { + continue; + } + + var input = eEditor.getEditorInput(); + if (editorInput.equals(input)) { + return eEditor; + } + } + } + + return null; + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEcoreSelection.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEcoreSelection.java new file mode 100644 index 00000000..82c70702 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperEcoreSelection.java @@ -0,0 +1,58 @@ +package org.emoflon.gips.debugger.utility; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.ui.editor.XtextEditor; + +public final class HelperEcoreSelection { + private HelperEcoreSelection() { + + } + + public static List selectionToEObjects(XtextEditor editor) { + return selectionToEObjects(editor, editor.getSelectionProvider().getSelection()); + } + + public static List selectionToEObjects(XtextEditor editor, ISelection selection) { + if (selection.isEmpty()) { + return Collections.emptyList(); + } + + if (selection instanceof IStructuredSelection structuredSelection) { + var selectedElements = structuredSelection.toList(); + var result = new ArrayList(selectedElements.size()); + + for (var element : selectedElements) { + if (element instanceof EObject eElement) { + result.add(eElement); + } + } + return result; + } + + if (selection instanceof ITextSelection textSelection) { + var selectedObject = editor.getDocument().readOnly(resource -> { + var parseResult = resource.getParseResult(); + var rootNode = parseResult.getRootNode(); + var currentNode = NodeModelUtils.findLeafNodeAtOffset(rootNode, textSelection.getOffset()); + var eObject = NodeModelUtils.findActualSemanticObjectFor(currentNode); + return eObject; + }); + + if (selectedObject == null) { + return Collections.emptyList(); + } + return Collections.singletonList(selectedObject); + } + + return Collections.emptyList(); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperErrorDialog.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperErrorDialog.java new file mode 100644 index 00000000..dcb4f3d9 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperErrorDialog.java @@ -0,0 +1,29 @@ +package org.emoflon.gips.debugger.utility; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; +import org.emoflon.gips.debugger.TracePlugin; + +public final class HelperErrorDialog { + private HelperErrorDialog() { + } + + public static MultiStatus createMultiStatus(String msg, Throwable t) { + List childStatuses = new ArrayList<>(); + StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace(); + + for (StackTraceElement stackTrace : stackTraces) { + var status = new Status(IStatus.ERROR, TracePlugin.PLUGIN_ID, stackTrace.toString()); + childStatuses.add(status); + } + + var ms = new MultiStatus(TracePlugin.PLUGIN_ID, IStatus.ERROR, childStatuses.toArray(new Status[] {}), + t.toString(), t); + return ms; + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperGipsl.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperGipsl.java new file mode 100644 index 00000000..4eda49c5 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/utility/HelperGipsl.java @@ -0,0 +1,98 @@ +package org.emoflon.gips.debugger.utility; + +import java.nio.file.Path; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.xtext.ui.editor.XtextEditor; +import org.eclipse.xtext.ui.editor.model.IXtextDocument; +import org.emoflon.gips.gipsl.gipsl.EditorGTFile; +import org.emoflon.gips.gipsl.gipsl.GipsConfig; + +public final class HelperGipsl { + private HelperGipsl() { + } + + public static IPath getLpPath(final XtextEditor editor) { + return getLpPath(editor.getDocument(), editor.getResource()); + } + + public static IPath getLpPath(final IXtextDocument document) { + return getLpPath(document, document.getAdapter(IResource.class)); + } + + public static IPath getLpPath(final IXtextDocument document, final IResource resource) { + final var path = getLpOutputPath_(document); + if (path == null) { + return null; + } + + final var ePath = IPath.fromPath(path); + + IPath targetPath; + + if (!ePath.isAbsolute()) { + final var project = resource.getProject(); + if (project != null) { + final var file = project.getFile(ePath); + targetPath = file.getFullPath(); + } else { + final var file = ResourcesPlugin.getWorkspace().getRoot().getFile(ePath); + targetPath = file.getFullPath(); + } + } else { + targetPath = ePath; + } + + return targetPath; + } + + public static String removeQuotes(String in) { + if (in == null || in.length() == 0) { + return in; + } + + if (in.length() == 1) { + if (in.charAt(0) == '\"') { + return ""; + } else { + return in; + } + } + + if (in.charAt(0) == '\"') { + if (in.charAt(in.length() - 1) == '\"') { + return in.substring(1, in.length() - 1); + } else { + return in.substring(1); + } + } else if (in.charAt(in.length() - 1) == '\"') { + return in.substring(0, in.length() - 1); + } + + return in; + } + + private static GipsConfig getConfig(IXtextDocument document) { + return document.readOnly(resource -> { + var editorFile = resource.getContents().stream().filter(EditorGTFile.class::isInstance) + .map(e -> (EditorGTFile) e).findAny(); + if (editorFile.isPresent()) { + return editorFile.get().getConfig(); + } + return null; + }); + } + + private static Path getLpOutputPath_(final IXtextDocument document) { + var config = getConfig(document); + var rawPath = config != null ? config.getPath() : null; + rawPath = removeQuotes(rawPath); + if ((rawPath == null) || rawPath.isBlank()) { + return null; + } + return Path.of(rawPath).normalize(); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/GipsTraceView.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/GipsTraceView.java new file mode 100644 index 00000000..a473f545 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/GipsTraceView.java @@ -0,0 +1,161 @@ +package org.emoflon.gips.debugger.view; + +import org.eclipse.jface.action.GroupMarker; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.IWorkbenchActionConstants; +import org.eclipse.ui.part.ViewPart; +import org.emoflon.gips.debugger.api.ITraceManager; +import org.emoflon.gips.debugger.api.event.ITraceManagerListener; +import org.emoflon.gips.debugger.api.event.TraceManagerEvent; +import org.emoflon.gips.debugger.view.model.INode; +import org.emoflon.gips.debugger.view.model.RootNode; + +/** + * View of the internal gips trace graph. + * + *

+ * The context menu can be extended via {@code org.eclipse.ui.menus} extension + * point. Location URI is popup:{@value #ID} + */ +public class GipsTraceView extends ViewPart { + + /** + * View part id {@value #ID}. + * + * Relevant extension points + *

    + *
  • org.eclipse.ui.views + *
+ */ + public static final String ID = "org.emoflon.gips.debugger.view"; + + private final ITraceManagerListener traceManagerListener = this::onTraceContextChange; + + private TreeViewer viewer; + private MenuManager menuManager; + private Menu menu; + + private RootNode viewModelRootNode; +// private IMemento memento; + +// @Inject +// IWorkbench workbench; + + public GipsTraceView() { + super(); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.ui/blob/master/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaOutlinePage.java + // https://git.eclipse.org/r/plugins/gitiles/platform/eclipse.platform.ui/+/0876b2212986e9daafa36e153f57dcdfc8949fbd/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/internal/ProblemView.java + + // TODO: memento can be used to restore the state of a view +// @Override +// public void init(IViewSite site, IMemento memento) throws PartInitException { +// super.init(site, memento); +// this.memento = memento; +// } + + @Override + public void saveState(IMemento memento) { + super.saveState(memento); +// saveSelection(memento); + } + + @Override + public void createPartControl(Composite parent) { + var layout = new FillLayout(); // new GridLayout(1, false); + parent.setLayout(layout); + + this.viewer = new TreeViewer(parent); + this.viewer.setContentProvider(new TraceContentProvider(this::refreshNode)); + this.viewer.setLabelProvider(new TraceLabelProvider()); +// ColumnViewerToolTipSupport.enableFor(this.viewer); + + ITraceManager traceManager = ITraceManager.getInstance(); + traceManager.addTraceManagerListener(traceManagerListener); + + this.viewModelRootNode = new RootNode(); + this.viewer.setInput(this.viewModelRootNode); + getSite().setSelectionProvider(this.viewer); + + menuManager = new MenuManager(); + menuManager.setRemoveAllWhenShown(true); + menuManager.addMenuListener(this::fillContextMenu); + menu = menuManager.createContextMenu(this.viewer.getControl()); + + this.viewer.getControl().setMenu(menu); + // allows context menu extensions + getSite().registerContextMenu(menuManager, this.viewer); + +// restoreSelection(this.memento); + } + + private void fillContextMenu(IMenuManager manager) { + manager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS)); + } + + @Override + public void dispose() { + ITraceManager traceManager = ITraceManager.getInstance(); + traceManager.removeTraceManagerListener(traceManagerListener); + + // dispose viewer model + for (var child : this.viewModelRootNode.childs.values()) { + if (traceManager.doesContextExist(child.contextId)) { + try { + traceManager.getContext(child.contextId).removeListener(child.listener); + child.listener = null; + } catch (Exception e) { + + } + } + } + this.viewModelRootNode = null; + + if (menu != null) + menu.dispose(); + menu = null; + + if (menuManager != null) + menuManager.dispose(); + menuManager = null; + + if (viewer != null) { + var contentProvider = this.viewer.getContentProvider(); + if (contentProvider != null) + contentProvider.dispose(); + + var labelProvider = this.viewer.getLabelProvider(); + if (labelProvider != null) + labelProvider.dispose(); + + this.viewer = null; + } + } + + private void onTraceContextChange(TraceManagerEvent event) { + refreshNode(this.viewModelRootNode); + } + + private void refreshNode(INode node) { + // needs to run on the UI-Thread + Display.getDefault().asyncExec(() -> { + if (this.viewer != null) + this.viewer.refresh(node); + }); + } + + @Override + public void setFocus() { + if (this.viewer != null) + this.viewer.getControl().setFocus(); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/TraceContentProvider.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/TraceContentProvider.java new file mode 100644 index 00000000..4c4a1ab7 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/TraceContentProvider.java @@ -0,0 +1,104 @@ +package org.emoflon.gips.debugger.view; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.emoflon.gips.debugger.api.ITraceContext; +import org.emoflon.gips.debugger.api.ITraceManager; +import org.emoflon.gips.debugger.view.model.ContextNode; +import org.emoflon.gips.debugger.view.model.INode; +import org.emoflon.gips.debugger.view.model.LinkModelNode; +import org.emoflon.gips.debugger.view.model.ModelNode; +import org.emoflon.gips.debugger.view.model.RootNode; + +final class TraceContentProvider implements ITreeContentProvider { + + private final Consumer refreshNode; + + public TraceContentProvider(Consumer refreshNode) { + this.refreshNode = Objects.requireNonNull(refreshNode, "refreshNode"); + } + + @Override + public Object[] getElements(Object inputElement) { + return getChildren(inputElement); + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof RootNode rNode) { + ITraceManager trace = ITraceManager.getInstance(); + var availableContextIds = trace.getAvailableContextIds(); + + rNode.childs.keySet().removeIf(trackedContextId -> !availableContextIds.contains(trackedContextId)); + + for (var contextId : availableContextIds) { + rNode.childs.computeIfAbsent(contextId, id -> { + var cNode = new ContextNode(rNode, id); + cNode.listener = event -> refreshNode.accept(cNode); + trace.getContext(id).addListener(cNode.listener); + return cNode; + }); + } + + return rNode.childs.values().stream().sorted().toArray(); + } + + if (parentElement instanceof ContextNode cNode) { + ITraceContext context = ITraceManager.getInstance().getContext(cNode.getContextId()); + return context.getAllModels().stream().sorted(String.CASE_INSENSITIVE_ORDER) + .map(id -> new ModelNode(cNode, id)).toArray(); + } + + if (parentElement instanceof ModelNode mNode) { + ITraceContext context = ITraceManager.getInstance().getContext(mNode.getContextId()); + + var incoming = context.getSourceModels(mNode.modelId).stream().sorted(String.CASE_INSENSITIVE_ORDER) + .map(id -> new LinkModelNode(mNode, id, LinkModelNode.Direction.BACKWARD)); + var outgoing = context.getTargetModels(mNode.modelId).stream().sorted(String.CASE_INSENSITIVE_ORDER) + .map(id -> new LinkModelNode(mNode, id, LinkModelNode.Direction.FORWARD)); + + return Stream.concat(incoming, outgoing).toArray(); + } + + return null; + } + + @Override + public Object getParent(Object element) { + if (element instanceof RootNode rNode) + return null; + + if (element instanceof ContextNode cNode) + return cNode.parent; + + if (element instanceof ModelNode mNode) + return mNode.parent; + + if (element instanceof LinkModelNode lmNode) + return lmNode.parent; + + return null; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof RootNode rNode) + return !ITraceManager.getInstance().getAvailableContextIds().isEmpty(); + + if (element instanceof ContextNode cNode) { + ITraceContext context = ITraceManager.getInstance().getContext(cNode.getContextId()); + return !context.getAllModels().isEmpty(); + } + + if (element instanceof ModelNode mNode) { + ITraceContext context = ITraceManager.getInstance().getContext(mNode.getContextId()); + return !context.getSourceModels(mNode.modelId).isEmpty() + || !context.getTargetModels(mNode.modelId).isEmpty(); + } + + return false; + } +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/TraceLabelProvider.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/TraceLabelProvider.java new file mode 100644 index 00000000..7f0f0e15 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/TraceLabelProvider.java @@ -0,0 +1,102 @@ +package org.emoflon.gips.debugger.view; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IToolTipProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; +import org.emoflon.gips.debugger.TracePlugin; +import org.emoflon.gips.debugger.service.ProjectTraceContext; +import org.emoflon.gips.debugger.trace.TraceModelLink; +import org.emoflon.gips.debugger.view.model.ContextNode; +import org.emoflon.gips.debugger.view.model.LinkModelNode; +import org.emoflon.gips.debugger.view.model.ModelNode; + +final class TraceLabelProvider extends LabelProvider implements ILabelProvider, IToolTipProvider { + + private Image projectImage; + private Image rightImage; + private Image leftImage; + + public TraceLabelProvider() { + ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); + this.projectImage = sharedImages.getImage(org.eclipse.ui.ide.IDE.SharedImages.IMG_OBJ_PROJECT); + this.rightImage = sharedImages.getImage(ISharedImages.IMG_TOOL_FORWARD); + this.leftImage = sharedImages.getImage(ISharedImages.IMG_TOOL_BACK); + } + + @Override + public Image getImage(Object element) { + if (element instanceof ContextNode) + return projectImage; + + if (element instanceof LinkModelNode node) { + return switch (node.direction) { + case FORWARD -> rightImage; + case BACKWARD -> leftImage; + default -> null; + }; + } + + return null; + } + + @Override + public String getText(Object element) { + String name = element != null ? element.toString() : "???"; + + if (element instanceof ModelNode node) { + + }else if(element instanceof LinkModelNode node) { + ProjectTraceContext context = TracePlugin.getInstance().getTraceManager().getContext(node.getContextId()); + switch(node.direction) { + case FORWARD: + { + TraceModelLink link = context.getModelLink(node.parent.modelId, node.modelId); + name = "Creates '" + name + " (maps " + link.getSourceNodeIds().size() + " to " + link.getTargetNodeIds().size() + " nodes)"; + break; + } + case BACKWARD: + { + TraceModelLink link = context.getModelLink(node.modelId, node.parent.modelId); + name = "Created by '" + name + " (maps " + link.getSourceNodeIds().size() + " to " + link.getTargetNodeIds().size() + " nodes)"; + break; + } + } + } + + return name; + } + + @Override + public void dispose() { + super.dispose(); + } + + @Override + public String getToolTipText(Object element) { + return "Tooltip (" + element + ")"; + } + +// @Override +// public Point getToolTipShift(Object object) { +// return new Point(5, 5); +// } +// +// @Override +// public int getToolTipDisplayDelayTime(Object object) { +// return 2000; +// } +// +// @Override +// public int getToolTipTimeDisplayed(Object object) { +// return 5000; +// } +// +// @Override +// public void update(ViewerCell cell) { +// cell.setText(cell.getElement().toString()); +// } + + } \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/menu/DeleteNode.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/menu/DeleteNode.java new file mode 100644 index 00000000..72f1b201 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/menu/DeleteNode.java @@ -0,0 +1,82 @@ +package org.emoflon.gips.debugger.view.menu; + +import org.eclipse.jface.action.ContributionItem; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.ui.ISelectionService; +import org.eclipse.ui.PlatformUI; +import org.emoflon.gips.debugger.TracePlugin; +import org.emoflon.gips.debugger.service.ProjectTraceContext; +import org.emoflon.gips.debugger.service.TraceManager; +import org.emoflon.gips.debugger.view.model.ContextNode; +import org.emoflon.gips.debugger.view.model.LinkModelNode; +import org.emoflon.gips.debugger.view.model.ModelNode; + +public class DeleteNode extends ContributionItem { + + public DeleteNode() { + } + + public DeleteNode(String id) { + super(id); + } + + @Override + public void fill(Menu menu, int index) { + ISelectionService selectionService = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService(); + if (!(selectionService.getSelection() instanceof IStructuredSelection structuredSelection)) + return; + + Object selectedElement = structuredSelection.getFirstElement(); + + if (selectedElement instanceof ContextNode node) { + MenuItem item = new MenuItem(menu, SWT.CHECK, index); + item.setText("Delete all"); + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + TraceManager manager = TracePlugin.getInstance().getTraceManager(); + manager.getContext(node.getContextId()).deleteAllModels(); + } + }); + } + + else if (selectedElement instanceof ModelNode node) { + MenuItem item = new MenuItem(menu, SWT.CHECK, index); + item.setText("Delete entry"); + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + TraceManager manager = TracePlugin.getInstance().getTraceManager(); + manager.getContext(node.getContextId()).deleteModel(node.modelId); + } + }); + } + + else if (selectedElement instanceof LinkModelNode node) { + MenuItem item = new MenuItem(menu, SWT.CHECK, index); + item.setText("Delete trace"); + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + ProjectTraceContext context = TracePlugin.getInstance().getTraceManager() + .getContext(node.getContextId()); + + switch (node.direction) { + case FORWARD: + context.deleteModelLink(node.parent.modelId, node.modelId); + break; + case BACKWARD: + context.deleteModelLink(node.modelId, node.parent.modelId); + break; + } + } + }); + } + + } +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/menu/OpenModelNodeInEditor.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/menu/OpenModelNodeInEditor.java new file mode 100644 index 00000000..aab45c3d --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/menu/OpenModelNodeInEditor.java @@ -0,0 +1,70 @@ +package org.emoflon.gips.debugger.view.menu; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.action.ContributionItem; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.ui.ISelectionService; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.emoflon.gips.debugger.utility.HelperEclipse; +import org.emoflon.gips.debugger.utility.HelperErrorDialog; +import org.emoflon.gips.debugger.view.model.ModelNode; + +public class OpenModelNodeInEditor extends ContributionItem { + + public OpenModelNodeInEditor() { + } + + public OpenModelNodeInEditor(String id) { + super(id); + } + + @Override + public void fill(Menu menu, int index) { + ISelectionService selectionService = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService(); + + if (!(selectionService.getSelection() instanceof IStructuredSelection structuredSelection)) + return; + + if (!(structuredSelection.getFirstElement() instanceof ModelNode modelNode)) + return; + + MenuItem item = new MenuItem(menu, SWT.CHECK, index); + item.setText("Open in editor"); + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + IProject project = HelperEclipse.tryAndGetProject(modelNode.getContextId()); + if (project == null) + return; + + IPath filePath = IPath.EMPTY; + for (String segment : modelNode.modelId.split("/")) + filePath = filePath.append(segment); + + IFile modelFile = project.getFile(filePath); + + try { + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + IDE.openEditor(page, modelFile); + } catch (final PartInitException ex) { + ex.printStackTrace(); + var error = HelperErrorDialog.createMultiStatus(ex.getLocalizedMessage(), ex); + ErrorDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Error", + "Unable to open file: " + modelFile, error); + } + } + }); + } + +} diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/ContextNode.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/ContextNode.java new file mode 100644 index 00000000..52ce429c --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/ContextNode.java @@ -0,0 +1,31 @@ +package org.emoflon.gips.debugger.view.model; + +import java.util.Objects; + +import org.emoflon.gips.debugger.api.event.ITraceUpdateListener; + +public class ContextNode implements INode, Comparable { + + public final RootNode parent; + public final String contextId; + public ITraceUpdateListener listener; + + public ContextNode(RootNode parent, String contextId) { + this.parent = Objects.requireNonNull(parent, "parent"); + this.contextId = Objects.requireNonNull(contextId, "contextId"); + } + + @Override + public String toString() { + return contextId; + } + + @Override + public int compareTo(ContextNode o) { + return String.CASE_INSENSITIVE_ORDER.compare(contextId, o.contextId); + } + + public String getContextId() { + return contextId; + } +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/INode.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/INode.java new file mode 100644 index 00000000..54363dfc --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/INode.java @@ -0,0 +1,5 @@ +package org.emoflon.gips.debugger.view.model; + +public interface INode { + +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/LinkModelNode.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/LinkModelNode.java new file mode 100644 index 00000000..1d4ffa33 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/LinkModelNode.java @@ -0,0 +1,29 @@ +package org.emoflon.gips.debugger.view.model; + +import java.util.Objects; + +public class LinkModelNode implements INode { + + public static enum Direction { + FORWARD, BACKWARD + } + + public final ModelNode parent; + public final String modelId; + public final Direction direction; + + public LinkModelNode(ModelNode parent, String modelId, Direction direction) { + this.parent = Objects.requireNonNull(parent, "parent"); + this.modelId = Objects.requireNonNull(modelId, "modelId"); + this.direction = direction; + } + + @Override + public String toString() { + return modelId; + } + + public String getContextId() { + return parent.getContextId(); + } +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/ModelNode.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/ModelNode.java new file mode 100644 index 00000000..e4ef7bb4 --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/ModelNode.java @@ -0,0 +1,23 @@ +package org.emoflon.gips.debugger.view.model; + +import java.util.Objects; + +public class ModelNode implements INode { + + public final ContextNode parent; + public final String modelId; + + public ModelNode(ContextNode parent, String modelId) { + this.parent = Objects.requireNonNull(parent, "parent"); + this.modelId = Objects.requireNonNull(modelId, "modelId"); + } + + @Override + public String toString() { + return modelId; + } + + public String getContextId() { + return parent.getContextId(); + } +} \ No newline at end of file diff --git a/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/RootNode.java b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/RootNode.java new file mode 100644 index 00000000..e7b3440d --- /dev/null +++ b/org.emoflon.gips.debugger/src/org/emoflon/gips/debugger/view/model/RootNode.java @@ -0,0 +1,10 @@ +package org.emoflon.gips.debugger.view.model; + +import java.util.HashMap; +import java.util.Map; + +public class RootNode implements INode { + + public final Map childs = new HashMap<>(); + +} \ No newline at end of file