diff --git a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerML2JSON.java b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerML2JSON.java index 278e137bf..6d60431ff 100644 --- a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerML2JSON.java +++ b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerML2JSON.java @@ -165,24 +165,23 @@ public void run(String[] args) { args = this.processArgs(args); if (args != null) { - - System.out.println("Saving " + args[0] + "..."); - - this.initialize(args); - this.read(args); - - System.out.println("Transforming" + - (this.isAddImplicitElements? " (adding implicit elements)... ": "...")); - this.transformAll(this.isAddImplicitElements); - - if (!this.isVerbose()) { - System.out.print("Processing"); - } - this.process(); - System.out.println(); - - System.out.println("Writing " + this.outputPath + "..."); try { + System.out.println("Saving " + args[0] + "..."); + + this.initialize(args); + this.read(args); + + System.out.println("Transforming" + + (this.isAddImplicitElements? " (adding implicit elements)... ": "...")); + this.transformAll(this.isAddImplicitElements); + + if (!this.isVerbose()) { + System.out.print("Processing"); + } + this.process(); + System.out.println(); + + System.out.println("Writing " + this.outputPath + "..."); this.write(); } catch (IOException e) { throw new RuntimeException(e); diff --git a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerMLRepositorySaveUtil.java b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerMLRepositorySaveUtil.java index 9e11c7266..38415bd1d 100644 --- a/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerMLRepositorySaveUtil.java +++ b/org.omg.kerml.xtext/src/org/omg/kerml/xtext/util/KerMLRepositorySaveUtil.java @@ -25,6 +25,7 @@ *****************************************************************************/ package org.omg.kerml.xtext.util; +import java.io.IOException; import java.nio.file.Paths; import java.util.Arrays; @@ -237,26 +238,30 @@ public void run(String[] args) { args = this.processArgs(args); if (args != null) { - System.out.println("Saving " + args[0] + "..."); - - this.initialize(args); - this.read(args); - - System.out.println("Transforming" + - (this.isAddImplicitElements? " (adding implicit generalizations)... ": "...")); - this.transformAll(this.isAddImplicitElements); - - System.out.println("\nBase path is " + this.getBasePath()); - System.out.println(); - - this.process(); - - if (isCommitted()) { - System.out.println("Saved to Project (" + this.getProjectName() + ") " + this.getProjectId()); - } else { - System.out.println("Failed to save Project (" + this.getProjectName() + ") "); + try { + System.out.println("Saving " + args[0] + "..."); + + this.initialize(args); + this.read(args); + + System.out.println("Transforming" + + (this.isAddImplicitElements? " (adding implicit generalizations)... ": "...")); + this.transformAll(this.isAddImplicitElements); + + System.out.println("\nBase path is " + this.getBasePath()); + System.out.println(); + + this.process(); + + if (isCommitted()) { + System.out.println("Saved to Project (" + this.getProjectName() + ") " + this.getProjectId()); + } else { + System.out.println("Failed to save Project (" + this.getProjectName() + ") "); + } + System.out.println(); + } catch (IOException e) { + throw new RuntimeException(e); } - System.out.println(); } } diff --git a/org.omg.sysml.interactive.tests/.launch/SysML Access Test.launch b/org.omg.sysml.interactive.tests/.launch/SysML Access Test.launch new file mode 100644 index 000000000..c5e18a0a2 --- /dev/null +++ b/org.omg.sysml.interactive.tests/.launch/SysML Access Test.launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.omg.sysml.interactive.tests/META-INF/MANIFEST.MF b/org.omg.sysml.interactive.tests/META-INF/MANIFEST.MF index e48ab309c..d33b705d1 100644 --- a/org.omg.sysml.interactive.tests/META-INF/MANIFEST.MF +++ b/org.omg.sysml.interactive.tests/META-INF/MANIFEST.MF @@ -9,4 +9,5 @@ Bundle-ActivationPolicy: lazy Require-Bundle: org.omg.sysml.interactive;bundle-version="0.3.2", org.junit;bundle-version="4.12.0", org.omg.sysml, - org.eclipse.xtext;bundle-version="2.22.0" + org.eclipse.xtext;bundle-version="2.22.0", + org.omg.sysml.xtext diff --git a/org.omg.sysml.interactive.tests/resources/org/omg/sysml/parseTest.sysml b/org.omg.sysml.interactive.tests/resources/org/omg/sysml/parseTest.sysml new file mode 100644 index 000000000..a96ffb10a --- /dev/null +++ b/org.omg.sysml.interactive.tests/resources/org/omg/sysml/parseTest.sysml @@ -0,0 +1,5 @@ +package P { + part def a { + part b; + } +} \ No newline at end of file diff --git a/org.omg.sysml.interactive.tests/resources/org/omg/sysml/parseTestWithError.sysml b/org.omg.sysml.interactive.tests/resources/org/omg/sysml/parseTestWithError.sysml new file mode 100644 index 000000000..18d034947 --- /dev/null +++ b/org.omg.sysml.interactive.tests/resources/org/omg/sysml/parseTestWithError.sysml @@ -0,0 +1,4 @@ +package P { + part def a { + part b; + } diff --git a/org.omg.sysml.interactive.tests/src/org/omg/sysml/SysMLAccessTest.java b/org.omg.sysml.interactive.tests/src/org/omg/sysml/SysMLAccessTest.java new file mode 100644 index 000000000..c6e1d475d --- /dev/null +++ b/org.omg.sysml.interactive.tests/src/org/omg/sysml/SysMLAccessTest.java @@ -0,0 +1,94 @@ +package org.omg.sysml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; +import org.junit.Test; +import org.omg.sysml.lang.sysml.Element; +import org.omg.sysml.xtext.util.SysMLAccess; +import org.omg.sysml.xtext.util.SysMLParseResult; + +public class SysMLAccessTest { + + private final static String SYSML_LIBRARY_PATH_KEY = "libraryPath"; + + public String getLibraryPath() { + return System.getProperty(SYSML_LIBRARY_PATH_KEY); + } + + @Test + public void testReadLibrary() throws IOException { + SysMLAccess sysmlAccess = SysMLAccess.createFullyFeatured(getLibraryPath()); + sysmlAccess.setVerbose(false); + sysmlAccess.loadLibrary(); + Set libraryResources = sysmlAccess.getLibraryResources(); + assertTrue("Libray was not loaded", !libraryResources.isEmpty()); + } + + @Test + public void testResolution() throws IOException { + SysMLAccess sysmlAccess = SysMLAccess.createFullyFeatured(getLibraryPath()); + sysmlAccess.setVerbose(false); + sysmlAccess.loadLibrary(); + + Element element = sysmlAccess.resolve("$::Parts::parts"); + assertNotNull(element); + assertEquals("Parts::parts", element.getQualifiedName()); + } + + @Test + public void testParsing() throws IOException { + SysMLAccess sysmlAccess = SysMLAccess.createFullyFeatured(getLibraryPath()); + sysmlAccess.setVerbose(false); + sysmlAccess.loadLibrary(); + + SysMLParseResult result = sysmlAccess.parse("myres.sysml", "package P { part b; }"); + assertTrue(result.getIssues().isEmpty()); + } + + @Test + public void testParsingWithError() throws IOException { + SysMLAccess sysmlAccess = SysMLAccess.createFullyFeatured(getLibraryPath()); + sysmlAccess.setVerbose(false); + sysmlAccess.loadLibrary(); + + SysMLParseResult result = sysmlAccess.parse("myres.sysml", "package P { part b; "); + assertFalse(result.getSyntaxErrors().isEmpty()); + } + + @Test + public void testParseFile() throws IOException, URISyntaxException { + SysMLAccess sysmlAccess = SysMLAccess.createFullyFeatured(getLibraryPath()); + sysmlAccess.setVerbose(false); + sysmlAccess.loadLibrary(); + + URL testFileURL = getClass().getResource("parseTest.sysml"); + File testFile = new File(testFileURL.toURI()); + List results = sysmlAccess.parseFiles(testFile, true); + + assertTrue(results.getFirst().getSyntaxErrors().isEmpty()); + } + + @Test + public void testParseFileWithError() throws IOException, URISyntaxException { + SysMLAccess sysmlAccess = SysMLAccess.createFullyFeatured(getLibraryPath()); + sysmlAccess.setVerbose(false); + sysmlAccess.loadLibrary(); + + URL testFileURL = getClass().getResource("parseTestWithError.sysml"); + File testFile = new File(testFileURL.toURI()); + List results = sysmlAccess.parseFiles(testFile, true); + + assertFalse(results.getFirst().getSyntaxErrors().isEmpty()); + } +} diff --git a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractive.java b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractive.java index 4bd8fbd77..b4d9d1898 100644 --- a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractive.java +++ b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractive.java @@ -40,25 +40,17 @@ import java.util.stream.Collectors; import org.eclipse.emf.common.util.BasicEList; -import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; -import org.eclipse.xtext.parser.IParseResult; -import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.resource.XtextResource; -import org.eclipse.xtext.scoping.IGlobalScopeProvider; -import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.validation.CheckMode; -import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; import org.omg.kerml.xtext.KerMLStandaloneSetup; import org.omg.kerml.xtext.xmi.KerMLxStandaloneSetup; -import org.omg.kerml.xtext.library.ILibraryIndexProvider; -import org.omg.kerml.xtext.naming.KerMLQualifiedNameConverter; import org.omg.sysml.execution.expressions.ExpressionEvaluator; import org.omg.sysml.lang.sysml.Element; import org.omg.sysml.lang.sysml.Expression; @@ -66,35 +58,34 @@ import org.omg.sysml.lang.sysml.Namespace; import org.omg.sysml.lang.sysml.RenderingUsage; import org.omg.sysml.lang.sysml.ResultExpressionMembership; -import org.omg.sysml.lang.sysml.SysMLFactory; import org.omg.sysml.lang.sysml.SysMLPackage; import org.omg.sysml.lang.sysml.Type; import org.omg.sysml.lang.sysml.ViewUsage; -import org.omg.sysml.lang.sysml.util.SysMLLibraryUtil; import org.omg.sysml.plantuml.SysML2PlantUMLLinkProvider; import org.omg.sysml.plantuml.SysML2PlantUMLSvc; import org.omg.sysml.util.SysMLUtil; import org.omg.sysml.util.TypeUtil; -import org.omg.sysml.util.repository.EObjectUUIDTracker; import org.omg.sysml.util.repository.APIModel; import org.omg.sysml.util.repository.EMFModelDelta; +import org.omg.sysml.util.repository.EMFModelRefresher; +import org.omg.sysml.util.repository.EObjectUUIDTracker; import org.omg.sysml.util.repository.ProjectRepository; -import org.omg.sysml.util.repository.Revision; import org.omg.sysml.util.repository.RemoteProject; import org.omg.sysml.util.repository.RemoteProject.RemoteBranch; -import org.omg.sysml.util.repository.EMFModelRefresher; +import org.omg.sysml.util.repository.Revision; import org.omg.sysml.util.traversal.Traversal; import org.omg.sysml.util.traversal.facade.impl.ApiElementProcessingFacade; import org.omg.sysml.util.traversal.facade.impl.JsonElementProcessingFacade; -import org.omg.sysml.xtext.xmi.SysMLxStandaloneSetup; import org.omg.sysml.xtext.SysMLStandaloneSetup; +import org.omg.sysml.xtext.util.StrictShadowingResourceDescriptionData; +import org.omg.sysml.xtext.util.SysMLAccess; +import org.omg.sysml.xtext.xmi.SysMLxStandaloneSetup; -import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Injector; -public class SysMLInteractive extends SysMLUtil { +public class SysMLInteractive extends SysMLAccess { public static final String HELP_KEY = "help"; public static final String PROJECT_ID_KEY = "id"; @@ -102,15 +93,6 @@ public class SysMLInteractive extends SysMLUtil { public static final String BRANCH_ID_KEY = "branch-id"; public static final String BRANCH_NAME_KEY = "branch"; - public static final String KERNEL_LIBRARIES_DIRECTORY = "Kernel Libraries"; - public static final String SYSTEMS_LIBRARY_DIRECTORY = "Systems Library"; - public static final String DOMAIN_LIBRARIES_DIRECTORY = "Domain Libraries"; - - public static final String KERML_EXTENSION = ".kerml"; - public static final String SYSML_EXTENSION = ".sysml"; - public static final String KERMLX_EXTENSION = ".kermlx"; - public static final String SYSMLX_EXTENSION = ".sysmlx"; - protected static Injector injector; protected static SysMLInteractive instance = null; @@ -123,39 +105,13 @@ public class SysMLInteractive extends SysMLUtil { protected SysML2PlantUMLSvc sysml2PlantUMLSvc; - private Resource dummyResource; - - @Inject - private IGlobalScopeProvider scopeProvider; - - @Inject - private KerMLQualifiedNameConverter qualifiedNameConverter; - - @Inject - private IResourceValidator validator; - - @Inject - private ILibraryIndexProvider libraryIndexCache; - private EObjectUUIDTracker tracker = new EObjectUUIDTracker(); @Inject private SysMLInteractive() { super(new StrictShadowingResourceDescriptionData()); } - - public void loadLibrary(String path) { - if (path != null) { - if (!path.endsWith("/")) { - path += "/"; - } - SysMLLibraryUtil.setModelLibraryDirectory(path); - this.readAll(path + KERNEL_LIBRARIES_DIRECTORY, false, KERML_EXTENSION); - this.readAll(path + SYSTEMS_LIBRARY_DIRECTORY, false, SYSML_EXTENSION); - this.readAll(path + DOMAIN_LIBRARIES_DIRECTORY, false, SYSML_EXTENSION); - } - } - + public void setApiBasePath(String apiBasePath) { this.apiBasePath = apiBasePath; } @@ -174,10 +130,6 @@ public Resource getResource() { return this.resource; } - public ILibraryIndexProvider getLibraryIndexCache() { - return libraryIndexCache; - } - public void removeResource() { if (this.resource != null) { try { @@ -191,13 +143,7 @@ public void removeResource() { public Element getRootElement() { Resource resource = this.getResource(); - if (resource instanceof XtextResource xtextResource) { - final IParseResult result = xtextResource.getParseResult(); - return result == null? null: (Element)result.getRootASTElement(); - } else { - EList contents = resource.getContents(); - return contents.isEmpty()? null: (Element)contents.get(0); - } + return getRootElement(resource); } public void parse(String input) throws IOException { @@ -215,29 +161,6 @@ public List validate() { validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl); } - private Resource getDummyResource() { - if (this.dummyResource == null) { - this.dummyResource = this.createResource("dummy" + SYSML_EXTENSION); - this.dummyResource.getContents().add(SysMLFactory.eINSTANCE.createNamespace()); - } - return this.dummyResource; - } - - public Element resolve(String name) { - IScope scope = scopeProvider.getScope( - this.getDummyResource(), - SysMLPackage.eINSTANCE.getNamespace_Member(), - Predicates.alwaysTrue()); - IEObjectDescription description = scope.getSingleElement( - this.qualifiedNameConverter.toQualifiedName(name)); - if (description == null) { - return null; - } else { - EObject object = description.getEObjectOrProxy(); - return object instanceof Element? (Element)object: null; - } - } - public SysMLInteractiveResult process(String input) { return process(input, true); } @@ -279,6 +202,15 @@ public String help(String command) { help(command, Collections.emptyList()); } + public void loadLibrary(String path) { + try { + setModelLibraryDirectory(path); + loadLibrary(); + } catch (IOException e) { + e.printStackTrace(); + } + } + public String repo(String apiBasePath, List help) { this.counter++; if (!help.isEmpty()) { @@ -327,7 +259,7 @@ public String eval(String input, String targetName, List help) { List elements = ExpressionEvaluator.INSTANCE.evaluate(expr, target); this.removeResource(); return elements == null? "": - elements.stream().map(SysMLInteractiveUtil::formatElement).collect(Collectors.joining()); + elements.stream().map(SysMLUtil::formatElement).collect(Collectors.joining()); } } @@ -346,9 +278,9 @@ public String listLibrary() { filter(Namespace.class::isInstance). flatMap(n->((Namespace)n).visibleMemberships(new BasicEList<>(), false, false).stream()). collect(Collectors.toList()); - return SysMLInteractiveUtil.formatMembershipList(globalMemberships); + return SysMLUtil.formatMembershipList(globalMemberships); } catch (Exception e) { - return SysMLInteractiveUtil.formatException(e); + return SysMLUtil.formatException(e); } } @@ -362,7 +294,7 @@ public String listQuery(String query) { } else { List memberships = ((Namespace)result.getRootElement()).getImportedMembership(); this.removeResource(); - return SysMLInteractiveUtil.formatMembershipList(memberships); + return SysMLUtil.formatMembershipList(memberships); } } @@ -395,12 +327,12 @@ else if (matchStyle(styles, "JSON")) { return processingFacade.toJsonTree(true); } else if (styles.isEmpty() || matchStyle(styles, "TREE")){ - return SysMLInteractiveUtil.formatTree(element); + return SysMLUtil.formatTree(element); } else { return "ERROR:Invalid style. Possible styles: TREE and JSON\n"; } } catch (Exception e) { - return SysMLInteractiveUtil.formatException(e); + return SysMLUtil.formatException(e); } } @@ -418,7 +350,7 @@ public Object export(String name, List help) { processingFacade.getTraversal().visit(element); return processingFacade.toJsonTree(true); } catch (Exception e) { - return SysMLInteractiveUtil.formatException(e); + return SysMLUtil.formatException(e); } } @@ -457,7 +389,7 @@ public String publish(String elementName, String projectName, String branchName, return "Saved to Project " + remoteProjectName + " (" + processingFacade.getProjectId() + ")\n"; } } catch (Exception e) { - return SysMLInteractiveUtil.formatException(e); + return SysMLUtil.formatException(e); } } @@ -854,7 +786,7 @@ public static SysMLInteractive getInstance() { return instance; } - public static void main(String[] args) { + public static void main(String[] args) throws IOException { System.out.println("SysML v2 Pilot Implementation"); SysMLInteractive instance = getInstance(); if (args.length > 0) { diff --git a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveLibraryIndexGenerator.java b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveLibraryIndexGenerator.java index 98b623cb8..bd26f022d 100644 --- a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveLibraryIndexGenerator.java +++ b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveLibraryIndexGenerator.java @@ -31,6 +31,7 @@ import org.eclipse.xtext.EcoreUtil2; import org.omg.kerml.xtext.library.LibraryIndex; import org.omg.sysml.util.ElementUtil; +import org.omg.sysml.xtext.util.SysMLAccess; import com.google.gson.GsonBuilder; @@ -52,10 +53,10 @@ public static void main(String[] args) throws IOException { //disable EMF reference clearing to prevent InterruptedException on early JVM shutdown System.setProperty("org.eclipse.emf.common.util.ReferenceClearingQueue", "false"); - SysMLInteractive instance = SysMLInteractive.getInstance(); - instance.getLibraryIndexCache().setIndexDisabled(true); - instance.loadLibrary(args[0]); + SysMLAccess instance = SysMLAccess.builder().libraryPath(args[0]).build(); + instance.getLibraryIndexCache().setIndexDisabled(true); + instance.loadLibrary(); ResourceSet rs = instance.getResourceSet(); diff --git a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveResult.java b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveResult.java index 424e0a510..0e4860d02 100644 --- a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveResult.java +++ b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveResult.java @@ -31,6 +31,7 @@ import org.eclipse.xtext.validation.Issue; import org.omg.sysml.lang.sysml.Element; import org.omg.sysml.lang.sysml.Namespace; +import org.omg.sysml.util.SysMLUtil; public class SysMLInteractiveResult { @@ -87,18 +88,18 @@ public String formatRootElement() { Element rootElement = this.getRootElement(); return rootElement == null? "": !(rootElement instanceof Namespace) || rootElement.getOwner() != null? - SysMLInteractiveUtil.formatElement(rootElement): + SysMLUtil.formatElement(rootElement): ((Namespace)rootElement).getOwnedMember().stream(). - map(SysMLInteractiveUtil::formatElement). + map(SysMLUtil::formatElement). collect(Collectors.joining()); } public String formatIssues() { - return SysMLInteractiveUtil.formatList(this.issues); + return SysMLUtil.formatList(this.issues); } public String formatException() { - return SysMLInteractiveUtil.formatException(this.exception); + return SysMLUtil.formatException(this.exception); } @Override @@ -108,11 +109,11 @@ public String toString() { } else { List syntaxErrors = this.getSyntaxErrors(); if (!syntaxErrors.isEmpty()) { - return SysMLInteractiveUtil.formatList(syntaxErrors); + return SysMLUtil.formatList(syntaxErrors); } else { List semanticErrors = this.getSemanticErrors(); List warnings = this.getWarnings(); - String msgs = SysMLInteractiveUtil.formatList(semanticErrors) + SysMLInteractiveUtil.formatList(warnings); + String msgs = SysMLUtil.formatList(semanticErrors) + SysMLUtil.formatList(warnings); return semanticErrors.isEmpty()? msgs + this.formatRootElement(): msgs; } } diff --git a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveUtil.java b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveUtil.java deleted file mode 100644 index 08412c9c8..000000000 --- a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/SysMLInteractiveUtil.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.omg.sysml.interactive; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; -import java.util.stream.Collectors; - -import org.omg.sysml.lang.sysml.Element; -import org.omg.sysml.lang.sysml.Expression; -import org.omg.sysml.lang.sysml.Feature; -import org.omg.sysml.lang.sysml.FeatureReferenceExpression; -import org.omg.sysml.lang.sysml.InvocationExpression; -import org.omg.sysml.lang.sysml.LiteralBoolean; -import org.omg.sysml.lang.sysml.LiteralInfinity; -import org.omg.sysml.lang.sysml.LiteralInteger; -import org.omg.sysml.lang.sysml.LiteralRational; -import org.omg.sysml.lang.sysml.LiteralString; -import org.eclipse.emf.ecore.EClass; -import org.omg.sysml.expressions.util.EvaluationUtil; -import org.omg.sysml.lang.sysml.CalculationUsage; -import org.omg.sysml.lang.sysml.Membership; -import org.omg.sysml.lang.sysml.MetadataFeature; -import org.omg.sysml.lang.sysml.OperatorExpression; -import org.omg.sysml.lang.sysml.OwningMembership; -import org.omg.sysml.lang.sysml.Relationship; -import org.omg.sysml.lang.sysml.Type; -import org.omg.sysml.util.TypeUtil; - -public class SysMLInteractiveUtil { - - public static final String INDENT = " "; - - private static String formatRelationship(EClass kind, String shortName, String memberName) { - return "[" + kind.getName() + - (shortName == null? "": " <" + shortName + ">") + - (memberName == null? "": " " + memberName) + - "] "; - } - - private static String formatRelationship(Relationship relationship) { - return relationship == null? "": - relationship instanceof Membership && !(relationship instanceof OwningMembership)? - formatRelationship(relationship.eClass(), - ((Membership)relationship).getMemberShortName(), - ((Membership)relationship).getMemberName()): - formatRelationship(relationship.eClass(), null, null); - } - - private static void formatElement(StringBuilder buffer, String indentation, Element element, String relationshipTag) { - buffer.append(indentation + relationshipTag + element.eClass().getName()); - if (EvaluationUtil.isMetaclassFeature(element)) { - formatElement(buffer, " ", ((MetadataFeature)element).getAnnotatedElement().get(0), ""); - } else { - String shortName = element.getDeclaredShortName(); - String name = nameOf(element); - buffer.append( - (shortName == null? "": " <" + shortName + ">") + - (name == null? "": " " + name) + - " (" + element.getElementId() + ")\n"); - } - } - - public static String nameOf(Element element) { - if (element == null) { - return ""; - } else if (element instanceof Feature && !((Feature)element).getOwnedFeatureChaining().isEmpty()) { - String name = ""; - for (Feature chainingFeature: ((Feature)element).getChainingFeature()) { - String nextName = chainingFeature.getName(); - if (nextName == null) { - nextName = ""; - } - if (name == "") { - name = nextName; - } else { - name += "." + nextName; - } - } - return name; - } else { - return element instanceof LiteralBoolean? Boolean.valueOf(((LiteralBoolean)element).isValue()).toString(): - element instanceof LiteralString? ((LiteralString)element).getValue().toString(): - element instanceof LiteralInteger? Integer.valueOf(((LiteralInteger)element).getValue()).toString(): - element instanceof LiteralRational? Double.valueOf(((LiteralRational)element).getValue()).toString(): - element instanceof LiteralInfinity? "*": - element instanceof FeatureReferenceExpression? nameOf(((FeatureReferenceExpression)element).getReferent()): - element instanceof OperatorExpression? ((OperatorExpression)element).getOperator(): - element instanceof InvocationExpression? nameOf(((InvocationExpression)element).getFunction()): - element.getName(); - } - } - - private static void formatExplicitElement(StringBuilder buffer, String indentation, Element element, Relationship relationship) { - formatElement(buffer, indentation, element, formatRelationship(relationship)); - } - - private static void formatImplicitElement(StringBuilder buffer, String indentation, Element element, EClass kind) { - formatElement(buffer, indentation, element, formatRelationship(kind, null, "(implicit)")); - } - - private static void formatTree(StringBuilder buffer, String indentation, Element element, Relationship relationship) { - formatExplicitElement(buffer, indentation, element, relationship); - if (element instanceof Expression && !(element instanceof CalculationUsage)) { - for (Element output: ((Expression)element).getOutput()) { - if (output.getOwner() == element) { - formatExplicitElement(buffer, indentation + SysMLInteractiveUtil.INDENT, output, output.getOwningMembership()); - } - } - } else { - if (element instanceof Type) { - TypeUtil.forEachImplicitGeneralTypeOf((Type)element, (kind, supertype)-> - formatImplicitElement(buffer, indentation + SysMLInteractiveUtil.INDENT, supertype, kind) - ); - } - - for (Relationship subrelationship: element.getOwnedRelationship()) { - for (Element relatedElement: subrelationship.getRelatedElement()) { - if (relatedElement != element) { - if (relatedElement.getOwningRelationship() == subrelationship) { - formatTree(buffer, indentation + SysMLInteractiveUtil.INDENT, relatedElement, subrelationship); - } else { - formatExplicitElement(buffer, indentation + SysMLInteractiveUtil.INDENT, relatedElement, subrelationship); - } - } - } - } - } - } - - public static String formatElement(Element element) { - StringBuilder buffer = new StringBuilder(); - formatExplicitElement(buffer, "", element, null); - return buffer.toString(); - } - - public static String formatTree(Element element) { - StringBuilder buffer = new StringBuilder(); - formatTree(buffer, "", element, null); - return buffer.toString(); - } - - public static String formatList(List list) { - StringBuilder buffer = new StringBuilder(); - list.stream().map(x->x.toString() + "\n").forEachOrdered(buffer::append); - return buffer.toString(); - } - - public static String formatException(Exception exception) { - StringWriter writer = new StringWriter(); - exception.printStackTrace(new PrintWriter(writer)); - return writer.toString(); - } - - public static String formatMembershipList(List membership) { - return membership.stream(). - map(Membership::getMemberElement). - sorted(SysMLInteractiveUtil::compare). - map(SysMLInteractiveUtil::formatElement). - collect(Collectors.joining()); - } - - public static int compare(Element element1, Element element2) { - String humanId1 = element1.getDeclaredShortName(); - String humanId2 = element2.getDeclaredShortName(); - String name1 = element1.getName(); - String name2 = element2.getName(); - return name1 != null && name2 != null? name1.compareToIgnoreCase(name2): - name1 == null && name2 != null? -1: - name1 != null && name2 == null? 1: - humanId1 != null && humanId2 != null? humanId1.compareToIgnoreCase(humanId2): - humanId1 == null && humanId2 != null? -1: - humanId1 != null && humanId2 == null? 1: - 0; - } - -} diff --git a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/VizResult.java b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/VizResult.java index f503d1788..27281a1ba 100644 --- a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/VizResult.java +++ b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/VizResult.java @@ -25,6 +25,8 @@ *****************************************************************************/ package org.omg.sysml.interactive; +import org.omg.sysml.util.SysMLUtil; + public class VizResult { public static enum Kind { EXCEPTION, @@ -62,7 +64,7 @@ public String formatException() { return this.exception == null? "": this.exception instanceof VizException? this.exception.getMessage(): this.exception instanceof IllegalArgumentException? "ERROR:" + this.exception.getMessage(): - SysMLInteractiveUtil.formatException(exception); + SysMLUtil.formatException(exception); } public String getPlantUML() { diff --git a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/profiler/SysMLInteractiveParsingProfiler.java b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/profiler/SysMLInteractiveParsingProfiler.java index ccfe29c10..718dcb7a9 100644 --- a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/profiler/SysMLInteractiveParsingProfiler.java +++ b/org.omg.sysml.interactive/src/org/omg/sysml/interactive/profiler/SysMLInteractiveParsingProfiler.java @@ -46,7 +46,8 @@ public static void main(String[] args) throws Exception { System.out.println("Usage: "); System.out.println("SysMLInteractiveParsingProfiler "); } - instance.loadLibrary(args[0]); + instance.setModelLibraryDirectory(args[0]); + instance.loadLibrary(); initWatch.stop(); System.out.println("Libraries loaded in " + initWatch.elapsed(TimeUnit.MILLISECONDS) + " ms"); diff --git a/org.omg.sysml.jupyter.kernel/pom.xml b/org.omg.sysml.jupyter.kernel/pom.xml index 449ce1e81..6bd026e68 100644 --- a/org.omg.sysml.jupyter.kernel/pom.xml +++ b/org.omg.sysml.jupyter.kernel/pom.xml @@ -22,6 +22,18 @@ * + + + org.omg.sysml + org.omg.sysml.xtext + ${project.version} + + + + * + * + + io.github.spencerpark diff --git a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/StrictShadowingResourceDescriptionData.java b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/StrictShadowingResourceDescriptionData.java similarity index 99% rename from org.omg.sysml.interactive/src/org/omg/sysml/interactive/StrictShadowingResourceDescriptionData.java rename to org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/StrictShadowingResourceDescriptionData.java index ffd225bed..cb4ffda47 100644 --- a/org.omg.sysml.interactive/src/org/omg/sysml/interactive/StrictShadowingResourceDescriptionData.java +++ b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/StrictShadowingResourceDescriptionData.java @@ -22,7 +22,7 @@ * Ed Seidewitz * *****************************************************************************/ -package org.omg.sysml.interactive; +package org.omg.sysml.xtext.util; import java.util.Collections; import java.util.LinkedHashMap; diff --git a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLAPIAccess.java b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLAPIAccess.java new file mode 100644 index 000000000..ed4f4425c --- /dev/null +++ b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLAPIAccess.java @@ -0,0 +1,254 @@ +package org.omg.sysml.xtext.util; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.omg.sysml.lang.sysml.Element; +import org.omg.sysml.util.repository.APIModel; +import org.omg.sysml.util.repository.EMFModelDelta; +import org.omg.sysml.util.repository.EMFModelRefresher; +import org.omg.sysml.util.repository.EObjectUUIDTracker; +import org.omg.sysml.util.repository.ProjectRepository; +import org.omg.sysml.util.repository.RemoteProject; +import org.omg.sysml.util.repository.Revision; +import org.omg.sysml.util.repository.RemoteProject.RemoteBranch; +import org.omg.sysml.util.traversal.Traversal; +import org.omg.sysml.util.traversal.facade.impl.ApiElementProcessingFacade; +import org.omg.sysml.util.traversal.facade.impl.JsonElementProcessingFacade; + +/** + * This API is used to publish or load models from the standard + * SysML REST and for JSON serialization of sysml models. + */ +public class SysMLAPIAccess { + + private SysMLAccess sysmlAccess; + private String repositoryURL; + + private EObjectUUIDTracker tracker = new EObjectUUIDTracker(); + + /* package */ SysMLAPIAccess(String apiBasePath, SysMLAccess sysmlAccess) { + this.repositoryURL = apiBasePath; + this.sysmlAccess = sysmlAccess; + } + + /** + * Sets the api base path for the repository + * + * @param apiBasePath + */ + public void setApibasePath(String apiBasePath) { + this.repositoryURL = apiBasePath; + } + + /** + * Publishes to the repository the model elements rooted in a named element + * + * @param element root element of the model + * @param projectName if null a new project is created named after the {@code element} otherwise it is used as the name of the project to create or update + * @param branchName if null the default branch is used otherwise the model is written to this branch of the project. + * @param includeDerievd include derived properties + */ + public void publish(Element element, String projectName, String branchName, boolean includeDerievd) { + assert repositoryURL != null: "ERROR: API base path is not set"; + if (!sysmlAccess.isInputResource(element.eResource())) { + throw new IllegalArgumentException("ERROR:'" + element.getQualifiedName() + "' is a library element"); + } else { + String remoteProjectName = projectName == null? element.getDeclaredName() : projectName; + + ApiElementProcessingFacade processingFacade = this.getApiElementProcessingFacade(remoteProjectName, branchName, includeDerievd); + processingFacade.getTraversal().visit(element); + processingFacade.commit(element); + } + } + + /** + * Publishes to the repository the resource's contents + * + * @param element resource to be published + * @param projectName if null a new project is created named after the {@code element} otherwise it is used as the name of the project to create or update + * @param branchName if null the default branch is used otherwise the model is written to this branch of the project. + * @param includeDerievd include derived properties + */ + public void publish(Resource resource, String projectName, String branchName, boolean includeDerievd) { + EObject first = resource.getContents().getFirst(); + if (first instanceof Element) { + publish((Element) first, projectName, branchName, includeDerievd); + } + } + + /** + * Publishes to the repository the model elements rooted in a named element + * + * @param element qualified name of the root element + * @param projectName if null a new project is created named after the {@code element} otherwise it is used as the name of the project to create or update + * @param branchName if null the default branch is used otherwise the model is written to this branch of the project. + * @param includeDerievd include derived properties + */ + public void publish(String elementName, String projectName, String branchName, boolean includeDerievd) { + Element element = sysmlAccess.resolve(elementName); + + if (element == null) { + throw new NoSuchElementException("ERROR:Couldn't resolve reference to Element '" + elementName); + } + + publish(element, projectName, branchName, includeDerievd); + } + + /** + * Downloads previously published models from a project in the repository. + * + * @param projectDescriptor to specify the project and the branch to be used. + * + * @return list of loaded resources + */ + public List load(APIProjectDescriptor projectDescriptor) { + + final ProjectRepository repository = new ProjectRepository(repositoryURL); + + final RemoteProject project = projectDescriptor.getBranchId().map(repository::getProjectById) + .or(() -> projectDescriptor.getProjectName().map(repository::getProjectByName)) + .orElseThrow(() -> new NoSuchElementException("ERROR:Project doesn't exist")); + + + final RemoteBranch branch; + + if (projectDescriptor.getBranchId().isEmpty() && projectDescriptor.getBranchName().isEmpty()) { + branch = project.getDefaultBranch(); + if (branch == null) { + throw new NoSuchElementException("ERROR:Project has no default branch"); + } + } else { + branch = projectDescriptor.getBranchId().map(id -> project.getBranch(UUID.fromString(id))) + .or(() -> projectDescriptor.getBranchName().map(project::getBranch)).orElseThrow(() -> new NoSuchElementException("ERROR:Branch doesn't exist")); + } + + return load(branch); + } + + private List load(RemoteBranch branch) { + assert repositoryURL != null: "ERROR: API base path is not set"; + + Revision headRevision = branch.getHeadRevision(); + if (!headRevision.isRemote()) { + throw new NoSuchElementException("ERROR:Branch has no head commit"); + } + + if (!tracker.isLibraryTracked()) { + System.out.println("Caching library UUIDs..."); + tracker.trackLibraryUUIDs(sysmlAccess.getLibraryResources()); + } + + tracker.clear(); + List inputResources = sysmlAccess.getInputResources(); + //UUIDS coming from resources that were added later in time will shadow previous ones + tracker.trackLocalUUIDs(inputResources); + + System.out.println("Downloading model..."); + APIModel model = headRevision.fetchRemote(); + + EMFModelRefresher modelRefresher = new EMFModelRefresher(model, tracker); + EMFModelDelta delta = modelRefresher.create(); + modelRefresher.getIssues().forEach(System.out::println); + + return delta.getProjectRootsAsNamespaces().stream().map(rootNs -> { + Resource resource = sysmlAccess.createResource(rootNs.toString() + SysMLAccess.SYSML_EXTENSION); + resource.getContents().add(rootNs); + sysmlAccess.addInputResource(resource); + sysmlAccess.addResourceToIndex(resource); + return resource; + }).toList(); + } + + + protected ApiElementProcessingFacade getApiElementProcessingFacade(String modelName, String branchName, boolean includeDerived) { + ApiElementProcessingFacade processingFacade = new ApiElementProcessingFacade(modelName, branchName, repositoryURL); + processingFacade.setIsIncludeDerived(includeDerived); + processingFacade.setTraversal(new Traversal(processingFacade)); + return processingFacade; + } + + /** + * Creates a JSON representation of the abstract syntax tree rooted in the named element. + * + * @param root root Element + * @param includeDerived include derived fields in the output + * @param asDelta wrap the elements in a delta as additions for direct REST API use + * @return JSON representation of the model + */ + public String toJson(Element root, boolean includeDerived, boolean asDelta) { + JsonElementProcessingFacade jsonFacade = new JsonElementProcessingFacade(); + jsonFacade.setIsIncludeDerived(includeDerived); + Traversal traversal = new Traversal(jsonFacade); + jsonFacade.setTraversal(traversal); + traversal.visit(root); + return jsonFacade.toJson(asDelta); + } + + /** + * Queries projects stored in the repository + * + * @return list of stored projects + */ + public List getProjects() { + final ProjectRepository repository = new ProjectRepository(repositoryURL); + return repository.getProjects(); + } + + /** + * Configuration for targeting a project and project branch in the repository. + * + * The {@code projectName} and {@code projectId} are mutually exclusive. + * The {@code branchName} and {@code branchId} are mutually exclusive. + */ + public static class APIProjectDescriptor { + private String projectName; + private String projectId; + private String branchName; + private String branchId; + + public APIProjectDescriptor projectId(String projectId) { + assert projectName == null: "ERROR:Project name and id cannot be provided at the same time"; + this.projectId = projectId; + return this; + } + + public APIProjectDescriptor projectName(String projectName) { + assert projectId == null: "ERROR:Project name and id cannot be provided at the same time"; + this.projectName = projectName; + return this; + } + + public APIProjectDescriptor branchName(String branchName) { + assert branchId == null: "ERROR:Branch name and id cannot be provided at the same time"; + this.branchName = branchName; + return this; + } + + public APIProjectDescriptor pranchId(String branchId) { + assert branchName == null: "ERROR:Branch name and id cannot be provided at the same time"; + this.branchId = branchId; + return this; + } + + public Optional getProjectName() { + return Optional.ofNullable(projectName); + } + + public Optional getProjectId() { + return Optional.ofNullable(projectId); + } + + public Optional getBranchName() { + return Optional.ofNullable(branchName); + } + + public Optional getBranchId() { + return Optional.ofNullable(branchId); + } + } +} diff --git a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLAccess.java b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLAccess.java new file mode 100644 index 000000000..ce9ff8fd8 --- /dev/null +++ b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLAccess.java @@ -0,0 +1,417 @@ +/** + * SysML 2 Pilot Implementation + * Copyright (C) 2025 Model Driven Solutions, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @license LGPL-3.0-or-later + * + * Contributors: + * Laszlo Gati, MDS + */ +package org.omg.sysml.xtext.util; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.parser.IParseResult; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.impl.ResourceDescriptionsData; +import org.eclipse.xtext.scoping.IGlobalScopeProvider; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.service.OperationCanceledError; +import org.eclipse.xtext.util.CancelIndicator; +import org.eclipse.xtext.validation.CheckMode; +import org.eclipse.xtext.validation.IResourceValidator; +import org.eclipse.xtext.validation.Issue; +import org.omg.kerml.xtext.KerMLStandaloneSetup; +import org.omg.kerml.xtext.library.ILibraryIndexProvider; +import org.omg.kerml.xtext.naming.KerMLQualifiedNameConverter; +import org.omg.kerml.xtext.xmi.KerMLxStandaloneSetup; +import org.omg.sysml.lang.sysml.Element; +import org.omg.sysml.lang.sysml.SysMLFactory; +import org.omg.sysml.lang.sysml.SysMLPackage; +import org.omg.sysml.lang.sysml.util.SysMLLibraryUtil; +import org.omg.sysml.util.SysMLUtil; +import org.omg.sysml.xtext.SysMLStandaloneSetup; +import org.omg.sysml.xtext.xmi.SysMLxStandaloneSetup; + +import com.google.common.base.Predicates; +import com.google.inject.Inject; +import com.google.inject.Injector; + +/** + * This is an API that enables building batch applications + * for parsing and validation of sysml and kerml models + * by setting up the language infrastructure and reading the library. + * The API provides access to the underlying EMF infrastructure as well to enable + * model processing. {@link SysMLAccess#getAPIAccess() } can be used to construct an + * interface for reading and writing models from/to the standard SysML REST API. + */ +public class SysMLAccess extends SysMLUtil { + public static final String KERNEL_LIBRARIES_DIRECTORY = "Kernel Libraries"; + public static final String SYSTEMS_LIBRARY_DIRECTORY = "Systems Library"; + public static final String DOMAIN_LIBRARIES_DIRECTORY = "Domain Libraries"; + + public static final String KERML_EXTENSION = ".kerml"; + public static final String SYSML_EXTENSION = ".sysml"; + + public static final String KERMLX_EXTENSION = ".kermlx"; + public static final String SYSMLX_EXTENSION = ".sysmlx"; + + private Resource dummyResource; + + @Inject + protected IGlobalScopeProvider scopeProvider; + + @Inject + protected KerMLQualifiedNameConverter qualifiedNameConverter; + + @Inject + private ILibraryIndexProvider libraryIndexCache; + + @Inject + protected IResourceValidator validator; + private SysMLAPIAccess apiAccess; + + @Inject + public SysMLAccess() { + super(); + } + + protected SysMLAccess(ResourceDescriptionsData resourceDescriptionData) { + super(resourceDescriptionData); + } + + /** + * Provides access for the library index. + * + * @return Library index provider + */ + public ILibraryIndexProvider getLibraryIndexCache() { + return libraryIndexCache; + } + + /** + * Initializes the language infrastructure for loading .kermlx and .sysmlx models. + */ + public void setupXMISupport() { + addExtension(KERMLX_EXTENSION); + addExtension(SYSMLX_EXTENSION); + KerMLxStandaloneSetup.doSetup(); + SysMLxStandaloneSetup.doSetup(); + } + + /** + * Loads the library from the specified directory. + * Use {@link SysMLAccess#setModelLibraryDirectory(String)} to set the directory path. + * + * @throws IOException + */ + public void loadLibrary() throws IOException { + doLoadLibrary(getModelLibraryDirectory()); + } + + protected void doLoadLibrary(String path) throws IOException { + if (path != null) { + this.readAll(path + KERNEL_LIBRARIES_DIRECTORY, false, KERML_EXTENSION); + this.readAll(path + SYSTEMS_LIBRARY_DIRECTORY, false, SYSML_EXTENSION); + this.readAll(path + DOMAIN_LIBRARIES_DIRECTORY, false, SYSML_EXTENSION); + } + } + + protected Resource getDummyResource() { + if (this.dummyResource == null) { + this.dummyResource = this.createResource("dummy" + SYSML_EXTENSION); + this.dummyResource.getContents().add(SysMLFactory.eINSTANCE.createNamespace()); + } + return this.dummyResource; + } + + /** + * Returns the root element from a resource + * + * @param resource resource with the root element + * @return root element + */ + public Element getRootElement(Resource resource) { + if (resource instanceof XtextResource xtextResource) { + final IParseResult result = xtextResource.getParseResult(); + return result == null? null: (Element)result.getRootASTElement(); + } else { + EList contents = resource.getContents(); + return contents.isEmpty()? null: (Element)contents.get(0); + } + } + + /** + * Resolves the given name from the loaded resources. + * + * @param name name to resolve + * @return resolved {@link Element} or null if name cannot be resolved + */ + public Element resolve(String name) { + IScope scope = scopeProvider.getScope( + this.getDummyResource(), + SysMLPackage.eINSTANCE.getNamespace_Member(), + Predicates.alwaysTrue()); + IEObjectDescription description = scope.getSingleElement( + this.qualifiedNameConverter.toQualifiedName(name)); + if (description == null) { + return null; + } else { + EObject object = description.getEObjectOrProxy(); + return object instanceof Element? (Element)object: null; + } + } + + /** + * Loads, parses and validates a model file or a directory (recursively) with a set of model files. + * + * @param file file to parse or directory containing model files + * @param isInput whether the file(s) read are to be considered input resources + * @return result of the parsing/validation + * @throws IOException + */ + public List parseFiles(File file, boolean isInput) throws IOException { + List resources = this.readAll(file, isInput); + return validateResources(resources); + } + + + /** + * Loads, parses and validates a model file or a directory (recursively) with a set of model files. + * + * @param file file to parse or directory containing model files + * @param isInput whether the file(s) read are to be considered input resources + * @return result of the parsing/validation + * @throws IOException + */ + public List parseFile(String file, boolean isInput) throws IOException { + return parseFiles(new File(file), isInput); + } + + + /** + * Validates the given resources + * + * @param resources resources to validate + * @return list of results + * @throws OperationCanceledError + */ + public List validateResources(List resources) throws OperationCanceledError { + final List results = new LinkedList<>(); + for (var resource: resources) { + List issues = validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl); + results.add(new SysMLParseResult(getRootElement(resource), issues)); + } + return results; + } + + /** + * Parses a string that contains a sysml or kerml model. + * The {@code resourceURI} identifies the loaded model in the {@code SysMLAccess}. + * It is mandatory to use either .sysml or .kerml extension {@code resourceURI} + * depending on the input to be parsed. + * + * @param resourceURI identifier of the parsed model in the {@code SysMLAccess} + * @param input string to be parsed + * @return results of the parsing + * @throws IOException + */ + public SysMLParseResult parse(String resourceURI, String input) throws IOException { + assert resourceURI.endsWith(".kerml") || resourceURI.endsWith(".sysml"): "resourceName must use .kerml or .sysml file extension"; + + Resource resource = this.createResource(resourceURI); + addInputResource(resource); + if (resource instanceof XtextResource xtextResource) { + xtextResource.reparse(input); + addResourceToIndex(xtextResource); + } else { + //TODO: add warning when resource is not meant to be parsed + } + + List issues = validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl); + return new SysMLParseResult(getRootElement(resource), issues); + } + + /** + * Changes the model library directory + * + * @param directory directory with the standard sysml library + */ + public void setModelLibraryDirectory(String directory) { + if (!directory.endsWith("/")) { + directory += "/"; + } + SysMLLibraryUtil.setModelLibraryDirectory(directory); + } + + /** + * @return path to set sysml standard library + */ + public String getModelLibraryDirectory() { + return SysMLLibraryUtil.getModelLibraryPath(); + } + + /** + * Returns the {@link SysMLAPIAccess}: a utility for accessing the SysML REST API. With basic read and write functionality. + * The instance is lazily created and stored on the first call further calls only modify the {@code apiBasePath}. + * + * @param apiBasePath API base path for the repository + * @return utility for writing/loading the repository + */ + public SysMLAPIAccess getOrCreateAPIAccess(String apiBasePath) { + if (apiAccess == null) { + this.apiAccess = new SysMLAPIAccess(apiBasePath, this); + } else { + this.apiAccess.setApibasePath(apiBasePath); + } + return this.apiAccess; + } + + /** + * Returns the stored {@link SysMLAPIAccess}. Use {@link SysMLAccess#getOrCreateAPIAccess(String)} + * for initialization. + * + * @return stored {@code SysMLAPIAccess} or null if it isn't initialized + */ + public SysMLAPIAccess getAPIAccess() { + return this.apiAccess; + } + + /** + * Returns a builder for the {@link SysMLAccess}. With more initialization options. + * + * @return builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Creates a {@link SysMLAccess} instance and initializes the language infrastructure as well as the API Access. + * + * @param modelLibraryDirectory directory with the standard sysml library + * @param apiBasePath API base path for the repository + * @return configured SysMLAccess instance + */ + public static SysMLAccess createFullyFeaturedWithAPIAccess(String modelLibraryDirectory, String apiBasePath) { + return builder() + .libraryPath(modelLibraryDirectory) + .apiBasePath(apiBasePath) + .xmiSupport() + .verbose() + .build(); + } + + /** + * Creates a {@link SysMLAccess} instance and initializes the language infrastructure. + * + * @param modelLibraryDirectory directory with the standard sysml library + * @return configured SysMLAccess instance + */ + public static SysMLAccess createFullyFeatured(String modelLibraryDirectory) { + return builder() + .libraryPath(modelLibraryDirectory) + .xmiSupport() + .verbose() + .build(); + } + + protected static class SysMLInteractiveAccess extends SysMLAccess { + + @Inject + public SysMLInteractiveAccess() { + super(new StrictShadowingResourceDescriptionData()); + } + } + + public static class Builder { + private boolean interactiveShadowing = false; + private String modelLibraryDirectory = SysMLLibraryUtil.DEFAULT_MODEL_LIBRARY_PATH; + private boolean withXMISupport = false; + private boolean verbose; + private String apiBasePath; + + public Builder libraryPath(String modelLibraryDirectory) { + this.modelLibraryDirectory = modelLibraryDirectory; + return this; + } + + public Builder interactiveShadowingSemantics() { + this.interactiveShadowing = true; + return this; + } + + public Builder xmiSupport() { + this.withXMISupport = true; + return this; + } + + public Builder verbose() { + this.verbose = true; + return this; + } + + /** + * Sets the api base path. This option also turns on {@link Builder#xmiSupport()} + * as it is needed for api usage. + * + * @param apiBasePath + * @return the builder + */ + public Builder apiBasePath(String apiBasePath) { + this.apiBasePath = apiBasePath; + return this; + } + + public SysMLAccess build() { + // Note: An EPackage must be registered to be sure the correctly configured + // CompositeEValidator is used. + EPackage.Registry.INSTANCE.put(SysMLPackage.eNS_URI, SysMLPackage.eINSTANCE); + + KerMLStandaloneSetup.doSetup(); + Injector injector = new SysMLStandaloneSetup().createInjectorAndDoEMFRegistration(); + + final SysMLAccess access; + if (interactiveShadowing) { + access = injector.getInstance(SysMLInteractiveAccess.class); + } else { + access = injector.getInstance(SysMLAccess.class); + } + + access.addExtension(SYSML_EXTENSION); + access.addExtension(KERML_EXTENSION); + + access.setModelLibraryDirectory(modelLibraryDirectory); + access.setVerbose(verbose); + //to create an instance + access.getOrCreateAPIAccess(apiBasePath); + + if (withXMISupport || apiBasePath != null) { + access.setupXMISupport(); + } + + return access; + } + } +} diff --git a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLParseResult.java b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLParseResult.java new file mode 100644 index 000000000..765725139 --- /dev/null +++ b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLParseResult.java @@ -0,0 +1,122 @@ +/***************************************************************************** + * SysML 2 Pilot Implementation + * Copyright (c) 2019, 2020 Model Driven Solutions, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of theGNU Lesser General Public License + * along with this program. If not, see . + * + * @license LGPL-3.0-or-later + * + * Contributors: + * Ed Seidewitz + * + *****************************************************************************/ +package org.omg.sysml.xtext.util; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.xtext.diagnostics.Severity; +import org.eclipse.xtext.validation.Issue; +import org.omg.sysml.lang.sysml.Element; +import org.omg.sysml.lang.sysml.Namespace; +import org.omg.sysml.util.SysMLUtil; + +public class SysMLParseResult { + + private Element rootElement = null; + private List issues = null; + private Exception exception = null; + + public SysMLParseResult(Element rootElement, List issues) { + this.rootElement = rootElement; + this.issues = issues; + } + + public SysMLParseResult(Exception exception) { + this.exception = exception; + } + + public Element getRootElement() { + return this.rootElement; + } + + public List getIssues() { + return this.issues == null? Collections.emptyList(): this.issues; + } + + public List getSyntaxErrors() { + return this.getIssues().stream().filter(Issue::isSyntaxError).collect(Collectors.toList()); + } + + public List getSemanticErrors() { + return this.getIssues().stream(). + filter(issue->!issue.isSyntaxError() && issue.getSeverity() == Severity.ERROR). + collect(Collectors.toList()); + } + + public List getWarnings() { + return this.getIssues().stream(). + filter(issue->issue.getSeverity().equals(Severity.WARNING)). + collect(Collectors.toList()); + } + + public boolean hasErrors() { + return this.getIssues().stream().anyMatch(issue->issue.getSeverity() == Severity.ERROR); + } + + public boolean hasWarnings() { + return this.getIssues().stream().anyMatch(issue->issue.getSeverity() == Severity.WARNING); + } + + public Exception getException() { + return this.exception; + } + + public String formatRootElement() { + Element rootElement = this.getRootElement(); + return rootElement == null? "": + !(rootElement instanceof Namespace) || rootElement.getOwner() != null? + SysMLUtil.formatElement(rootElement): + ((Namespace)rootElement).getOwnedMember().stream(). + map(SysMLUtil::formatElement). + collect(Collectors.joining()); + } + + public String formatIssues() { + return SysMLUtil.formatList(this.issues); + } + + public String formatException() { + return SysMLUtil.formatException(this.exception); + } + + @Override + public String toString() { + if (this.exception != null) { + return this.formatException(); + } else { + List syntaxErrors = this.getSyntaxErrors(); + if (!syntaxErrors.isEmpty()) { + return SysMLUtil.formatList(syntaxErrors); + } else { + List semanticErrors = this.getSemanticErrors(); + List warnings = this.getWarnings(); + String msgs = SysMLUtil.formatList(semanticErrors) + SysMLUtil.formatList(warnings); + return semanticErrors.isEmpty()? msgs + this.formatRootElement(): msgs; + } + } + } + +} diff --git a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLRepositoryLoadUtil.java b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLRepositoryLoadUtil.java index a5953972f..7fc419ab9 100644 --- a/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLRepositoryLoadUtil.java +++ b/org.omg.sysml.xtext/src/org/omg/sysml/xtext/util/SysMLRepositoryLoadUtil.java @@ -144,23 +144,23 @@ public void load() throws UnsupportedOperationException, ApiException { return; } - System.out.println("Reading library..."); - readAll(localLibraryPath, false); - - //collect ids from library - System.out.println("Tracking library UUIDs..."); - EObjectUUIDTracker tracker = new EObjectUUIDTracker(); - tracker.trackLibraryUUIDs(getLibraryResources()); - - System.out.println("Downloading project..."); - Revision headRevision = branch.getHeadRevision(); - APIModel remote = headRevision.fetchRemote(); - EMFModelRefresher modelRefresher = new EMFModelRefresher(remote, tracker); - EMFModelDelta delta = modelRefresher.create(); - modelRefresher.getIssues().forEach(System.out::println); - ResourceSet resourceSet = getResourceSet(); - try { + System.out.println("Reading library..."); + readAll(localLibraryPath, false); + + //collect ids from library + System.out.println("Tracking library UUIDs..."); + EObjectUUIDTracker tracker = new EObjectUUIDTracker(); + tracker.trackLibraryUUIDs(getLibraryResources()); + + System.out.println("Downloading project..."); + Revision headRevision = branch.getHeadRevision(); + APIModel remote = headRevision.fetchRemote(); + EMFModelRefresher modelRefresher = new EMFModelRefresher(remote, tracker); + EMFModelDelta delta = modelRefresher.create(); + modelRefresher.getIssues().forEach(System.out::println); + ResourceSet resourceSet = getResourceSet(); + System.out.println("Saving resources..."); delta.apply(resourceSet, URI.createFileURI(targetLocation)); System.out.println("Done."); diff --git a/org.omg.sysml/src/org/omg/sysml/util/SysMLUtil.java b/org.omg.sysml/src/org/omg/sysml/util/SysMLUtil.java index 82b2f2bd7..e71e86b17 100644 --- a/org.omg.sysml/src/org/omg/sysml/util/SysMLUtil.java +++ b/org.omg.sysml/src/org/omg/sysml/util/SysMLUtil.java @@ -26,13 +26,20 @@ package org.omg.sysml.util; import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; @@ -41,7 +48,25 @@ import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.impl.ResourceDescriptionsData; +import org.omg.sysml.expressions.util.EvaluationUtil; +import org.omg.sysml.lang.sysml.CalculationUsage; +import org.omg.sysml.lang.sysml.Element; +import org.omg.sysml.lang.sysml.Expression; +import org.omg.sysml.lang.sysml.Feature; +import org.omg.sysml.lang.sysml.FeatureReferenceExpression; +import org.omg.sysml.lang.sysml.InvocationExpression; +import org.omg.sysml.lang.sysml.LiteralBoolean; +import org.omg.sysml.lang.sysml.LiteralInfinity; +import org.omg.sysml.lang.sysml.LiteralInteger; +import org.omg.sysml.lang.sysml.LiteralRational; +import org.omg.sysml.lang.sysml.LiteralString; +import org.omg.sysml.lang.sysml.Membership; +import org.omg.sysml.lang.sysml.MetadataFeature; +import org.omg.sysml.lang.sysml.OperatorExpression; +import org.omg.sysml.lang.sysml.OwningMembership; +import org.omg.sysml.lang.sysml.Relationship; import org.omg.sysml.lang.sysml.SysMLPackage; +import org.omg.sysml.lang.sysml.Type; import com.google.common.base.Predicates; @@ -60,7 +85,7 @@ public abstract class SysMLUtil { private final ResourceSet resourceSet; private final List inputResources = new LinkedList<>(); - private final List extensions = new ArrayList(); + private final Set extensions = new HashSet<>(); private final ResourceDescriptionsData index; private boolean isVerbose = true; @@ -230,21 +255,45 @@ public Resource readResource(final String path) { * * @param file the file from which the resources are be read * @param isInput whether the resources read are to be considered input resources + * @param extensions the allowed file extensions + * @return + * @throws IOException */ - public void readAll(final File file, boolean isInput) { - if (file.isDirectory()) { - for (File nestedFile: file.listFiles()) { - this.readAll(nestedFile, isInput); + public List readAll(final File file, final boolean isInput, Set extensions) throws IOException { + List resources = new LinkedList<>(); + Files.walk(file.toPath()).forEach(path -> { + Resource res = read(path, isInput, extensions); + if (res != null) { + resources.add(res); } - } else { - final String path = file.getPath(); - if (extensions.stream().anyMatch(path::endsWith)) { - Resource resource = this.readResource(file.getPath()); - if (isInput) { - this.addInputResource(resource); - } + }); + return resources; + } + + /** + * If the given file has an allowable extension, then read it. Or, if the file is a directory, then + * recursively read all the allowable files in it, directly or indirectly. + * + * @param file the file from which the resources are be read + * @param isInput whether the resources read are to be considered input resources + * @return + * @throws IOException + */ + public List readAll(final File file, final boolean isInput) throws IOException { + return readAll(file, isInput, extensions); + } + + public Resource read(final Path path, boolean isInput, Set extensions) { + final Resource resource; + if (extensions.stream().anyMatch(path.toString()::endsWith)) { + resource = this.readResource(path.toString()); + if (isInput) { + this.addInputResource(resource); } + } else { + resource = null; } + return resource; } /** @@ -254,9 +303,11 @@ public void readAll(final File file, boolean isInput) { * * @param path the path from which resources are to be read * @param isInput whether the resources read are to be considered input resources + * @return + * @throws IOException */ - public void readAll(final String path, boolean isInput) { - this.readAll(new File(path), isInput); + public List readAll(final String path, boolean isInput) throws IOException { + return this.readAll(new File(path), isInput, this.extensions); } /** @@ -267,11 +318,11 @@ public void readAll(final String path, boolean isInput) { * @param path the path from which resources are to be read * @param isInput whether the resources read are to be considered input resources * @param extension the allowed file extension + * @return + * @throws IOException */ - public void readAll(final String path, boolean isInput, String extension) { - this.extensions.clear(); - this.addExtension(extension); - this.readAll(new File(path), isInput); + public List readAll(final String path, boolean isInput, String extension) throws IOException { + return this.readAll(new File(path), isInput, Set.of(extension)); } /** @@ -280,8 +331,9 @@ public void readAll(final String path, boolean isInput, String extension) { * other paths are considered to be library resources. * * @param paths the paths from which resources are to be read + * @throws IOException */ - public void read(final String... paths) { + public void read(final String... paths) throws IOException { if (paths.length > 0) { for (int i = 1; i < paths.length; i++) { this.readAll(paths[i], false); @@ -312,4 +364,151 @@ public void resolveAllInputResources() { } } + //Utility for printing out parts of the model in a human readable format + + public static final String INDENT = " "; + + private static String formatRelationship(EClass kind, String shortName, String memberName) { + return "[" + kind.getName() + + (shortName == null? "": " <" + shortName + ">") + + (memberName == null? "": " " + memberName) + + "] "; + } + + private static String formatRelationship(Relationship relationship) { + return relationship == null? "": + relationship instanceof Membership && !(relationship instanceof OwningMembership)? + formatRelationship(relationship.eClass(), + ((Membership)relationship).getMemberShortName(), + ((Membership)relationship).getMemberName()): + formatRelationship(relationship.eClass(), null, null); + } + + private static void formatElement(StringBuilder buffer, String indentation, Element element, String relationshipTag) { + buffer.append(indentation + relationshipTag + element.eClass().getName()); + if (EvaluationUtil.isMetaclassFeature(element)) { + formatElement(buffer, " ", ((MetadataFeature)element).getAnnotatedElement().get(0), ""); + } else { + String shortName = element.getDeclaredShortName(); + String name = nameOf(element); + buffer.append( + (shortName == null? "": " <" + shortName + ">") + + (name == null? "": " " + name) + + " (" + element.getElementId() + ")\n"); + } + } + + public static String nameOf(Element element) { + if (element == null) { + return ""; + } else if (element instanceof Feature && !((Feature)element).getOwnedFeatureChaining().isEmpty()) { + String name = ""; + for (Feature chainingFeature: ((Feature)element).getChainingFeature()) { + String nextName = chainingFeature.getName(); + if (nextName == null) { + nextName = ""; + } + if (name == "") { + name = nextName; + } else { + name += "." + nextName; + } + } + return name; + } else { + return element instanceof LiteralBoolean? Boolean.valueOf(((LiteralBoolean)element).isValue()).toString(): + element instanceof LiteralString? ((LiteralString)element).getValue().toString(): + element instanceof LiteralInteger? Integer.valueOf(((LiteralInteger)element).getValue()).toString(): + element instanceof LiteralRational? Double.valueOf(((LiteralRational)element).getValue()).toString(): + element instanceof LiteralInfinity? "*": + element instanceof FeatureReferenceExpression? nameOf(((FeatureReferenceExpression)element).getReferent()): + element instanceof OperatorExpression? ((OperatorExpression)element).getOperator(): + element instanceof InvocationExpression? nameOf(((InvocationExpression)element).getFunction()): + element.getName(); + } + } + + private static void formatExplicitElement(StringBuilder buffer, String indentation, Element element, Relationship relationship) { + formatElement(buffer, indentation, element, formatRelationship(relationship)); + } + + private static void formatImplicitElement(StringBuilder buffer, String indentation, Element element, EClass kind) { + formatElement(buffer, indentation, element, formatRelationship(kind, null, "(implicit)")); + } + + private static void formatTree(StringBuilder buffer, String indentation, Element element, Relationship relationship) { + formatExplicitElement(buffer, indentation, element, relationship); + if (element instanceof Expression && !(element instanceof CalculationUsage)) { + for (Element output: ((Expression)element).getOutput()) { + if (output.getOwner() == element) { + formatExplicitElement(buffer, indentation + SysMLUtil.INDENT, output, output.getOwningMembership()); + } + } + } else { + if (element instanceof Type) { + TypeUtil.forEachImplicitGeneralTypeOf((Type)element, (kind, supertype)-> + formatImplicitElement(buffer, indentation + SysMLUtil.INDENT, supertype, kind) + ); + } + + for (Relationship subrelationship: element.getOwnedRelationship()) { + for (Element relatedElement: subrelationship.getRelatedElement()) { + if (relatedElement != element) { + if (relatedElement.getOwningRelationship() == subrelationship) { + formatTree(buffer, indentation + SysMLUtil.INDENT, relatedElement, subrelationship); + } else { + formatExplicitElement(buffer, indentation + SysMLUtil.INDENT, relatedElement, subrelationship); + } + } + } + } + } + } + + public static String formatElement(Element element) { + StringBuilder buffer = new StringBuilder(); + formatExplicitElement(buffer, "", element, null); + return buffer.toString(); + } + + public static String formatTree(Element element) { + StringBuilder buffer = new StringBuilder(); + formatTree(buffer, "", element, null); + return buffer.toString(); + } + + public static String formatList(List list) { + StringBuilder buffer = new StringBuilder(); + list.stream().map(x->x.toString() + "\n").forEachOrdered(buffer::append); + return buffer.toString(); + } + + public static String formatException(Exception exception) { + StringWriter writer = new StringWriter(); + exception.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + + public static String formatMembershipList(List membership) { + return membership.stream(). + map(Membership::getMemberElement). + sorted(SysMLUtil::compare). + map(SysMLUtil::formatElement). + collect(Collectors.joining()); + } + + public static int compare(Element element1, Element element2) { + String humanId1 = element1.getDeclaredShortName(); + String humanId2 = element2.getDeclaredShortName(); + String name1 = element1.getName(); + String name2 = element2.getName(); + return name1 != null && name2 != null? name1.compareToIgnoreCase(name2): + name1 == null && name2 != null? -1: + name1 != null && name2 == null? 1: + humanId1 != null && humanId2 != null? humanId1.compareToIgnoreCase(humanId2): + humanId1 == null && humanId2 != null? -1: + humanId1 != null && humanId2 == null? 1: + 0; + } + }