diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..5b034430 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,117 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + push: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build_package: + name: Builld chocosolver package + runs-on: ubuntu-20.04 + steps: + #### This step is only needed for GHA local runner, act: https://github.com/nektos/act + ### That is because act ubuntu image does not have mvn installed, but the github one does + # - name: Install curl (for nektos/act local CI testing) + # run: apt-get update && apt-get install build-essential curl pkg-config openssl -y + # - name: Download Maven + # run: | + # curl -sL https://downloads.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.zip -o maven.zip + # apt-get update + # apt-get -y install unzip + # unzip -d /usr/share maven.zip + # rm maven.zip + # ln -s /usr/share/apache-maven-3.8.8/bin/mvn /usr/bin/mvn + # echo "M2_HOME=/usr/share/apache-maven-3.8.8" | tee -a /etc/environment + # file /usr/bin/mvn + # echo $PATH + # Rest of the actions + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + - run: mvn --batch-mode --update-snapshots verify + - run: mkdir staging && cp target/*.jar staging + - uses: actions/upload-artifact@v4 + with: + name: chocosolver-release + path: staging/*.jar + retention-days: 7 + + test_sysml_export: + name: Test SysMLv2 export + needs: build_package + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - name: Download Chocosolver package + uses: actions/download-artifact@v4 + with: + name: chocosolver-release + path: /tmp/chocosolver-release + - name: Install clafer tools + run: | + wget --no-check-certificate https://gsd.uwaterloo.ca/clafer-tools-bin/clafer-tools-0.4.5-linux-x86_64.zip + unzip clafer-tools-0.4.5-linux-x86_64.zip + mv clafer-tools-0.4.5 /opt/clafer-tools-0.4.5 + echo "/opt/clafer-tools-0.4.5" >> $GITHUB_PATH + - name: Process SysML examples + shell: bash + run: | + for f in examples/sysml-samples/*.cfr + do + echo "Processing $f" + java -jar /tmp/chocosolver-release/chocosolver-0.4.4-jar-with-dependencies.jar --sysml --file $f --output $f.sysml + done + - uses: actions/upload-artifact@v4 + with: + name: SysML_generated + path: examples/sysml-samples/*.sysml + retention-days: 7 + + test_plantuml_export: + name: Test PlantUML export + needs: build_package + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - name: Download Chocosolver package + uses: actions/download-artifact@v4 + with: + name: chocosolver-release + path: /tmp/chocosolver-release + - name: Install clafer tools + shell: bash + run: | + wget --no-check-certificate https://gsd.uwaterloo.ca/clafer-tools-bin/clafer-tools-0.4.5-linux-x86_64.zip + unzip clafer-tools-0.4.5-linux-x86_64.zip + mv clafer-tools-0.4.5 /opt/clafer-tools-0.4.5 + echo "/opt/clafer-tools-0.4.5" >> $GITHUB_PATH + - name: Install PlantUML + run: | + wget https://github.com/plantuml/plantuml/releases/download/v1.2023.4/plantuml-1.2023.4.jar + - name: Install graphviz + run: | + sudo apt-get update + sudo apt-get install graphviz + - name: Process SysML examples + run: | + for f in examples/**.cfr + do + echo "Processing $f" + java -jar /tmp/chocosolver-release/chocosolver-0.4.4-jar-with-dependencies.jar --plantuml --file $f --output $f.plantuml + java -jar /tmp/chocosolver-release/chocosolver-0.4.4-jar-with-dependencies.jar --sysml --file $f --output $f.sysml + echo "Rendering $f.plantuml" + java -jar plantuml-1.2023.4.jar $f.plantuml + done + - uses: actions/upload-artifact@v4 + with: + name: PlantUML_generated + path: | + examples/**.plantuml + examples/**.sysml + examples/**.png + retention-days: 7 diff --git a/.gitignore b/.gitignore index 4df54bb6..1522af68 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ target nb-configuration.xml nbactions.xml src/test/resources/user.properties +.idea +*.plantuml +*.js + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..6ee3c30a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,91 @@ +stages: + - build + - test + - deploy + +image: maven:3.6-jdk-11 + +before_script: + - export PATH="$PWD/clafer-tools-0.4.5:$PATH" + +prepare_environment: + stage: build + script: + - wget --no-check-certificate https://gsd.uwaterloo.ca/clafer-tools-bin/clafer-tools-0.4.5-linux-x86_64.zip + - unzip clafer-tools-0.4.5-linux-x86_64.zip + artifacts: + when: always + paths: + - clafer-tools-0.4.5/ + expire_in: 1 hour + +build_package: + stage: build + tags: ["stardust"] + script: + - mvn package + artifacts: + when: always + paths: + - target/chocosolver-0.4.4-jar-with-dependencies.jar + expire_in: 1 week + +test_sysml_export: + stage: test + needs: + - job: build_package + artifacts: true + - job: prepare_environment + artifacts: true + script: + - | + for f in examples/sysml-samples/*.cfr + do + echo "Processing $f" + java -jar target/chocosolver-0.4.4-jar-with-dependencies.jar --sysml --file $f --output $f.sysml + done + artifacts: + when: always + paths: + - ./examples/sysml-samples/*.sysml + expire_in: 1 week + +test_plantuml_export: + stage: test + needs: + - job: build_package + artifacts: true + - job: prepare_environment + artifacts: true + script: + - wget https://github.com/plantuml/plantuml/releases/download/v1.2023.4/plantuml-1.2023.4.jar + - apt-get update && apt-get install -y graphviz + - | + for f in examples/**/*.cfr + do + echo "Processing $f" + java -jar target/chocosolver-0.4.4-jar-with-dependencies.jar --plantuml --file $f --output $f.plantuml + java -jar target/chocosolver-0.4.4-jar-with-dependencies.jar --sysml --file $f --output $f.sysml + echo "Rendering $f.plantuml" + java -jar plantuml-1.2023.4.jar $f.plantuml + done + artifacts: + when: always + paths: + - ./examples/*.plantuml + - ./examples/*.sysml + - ./examples/*.png + expire_in: 1 week + +deploy_job: + stage: deploy + needs: + - job: build_package + artifacts: true + - job: prepare_environment + artifacts: true + script: + - 'echo "Deploying the project to GitLab Package Registry..."' + - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ./target/chocosolver-0.4.4-jar-with-dependencies.jar "https://gitlab-ext.galois.com/api/v4/projects/$CI_PROJECT_ID/packages/maven/"' + only: + - tags diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 334c5e23..00000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: java -sudo: false - -jdk: - - oraclejdk8 - -install: - - cd $HOME - - git clone --depth=50 https://github.com/chocoteam/choco3.git chocoteam/choco3 - - cd chocoteam/choco3 - - mvn install -DskipTests - - rm -rf $HOME/chocoteam/choco3 - -before_script: cd $TRAVIS_BUILD_DIR - -script: mvn test -DargLine="-Xmx1024m" diff --git a/README.md b/README.md index 137c3003..d2fec28f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://secure.travis-ci.org/gsdlab/chocosolver.svg)](http://travis-ci.org/gsdlab/chocosolver) +[![Build Status](https://github.com/EthanJamesLew/chocosolver-sysml/actions/workflows/maven.yml/badge.svg)](https://github.com/EthanJamesLew/chocosolver-sysml/actions/workflows/maven.yml) # chocosolver diff --git a/examples/AndroidSampleMoo_2.cfr b/examples/AndroidSampleMoo_2.cfr new file mode 100644 index 00000000..69e3ff2e --- /dev/null +++ b/examples/AndroidSampleMoo_2.cfr @@ -0,0 +1,58 @@ +abstract Component + performance : integer + +abstract ComponentWithEnergy : Component + energy : integer + +abstract androidPhone + hardware : Component + [ performance = 0 ] + screen : Component + [ performance = 0 ] + xor material : Component + [ performance = 0 ] + oled : ComponentWithEnergy + [ energy = 3] + [ performance = -3 ] + amoled : ComponentWithEnergy + [ energy = 2 ] + [ performance = -5 ] + lcd : ComponentWithEnergy + [ energy = 4 ] + [ performance = -2 ] + keyboard : ComponentWithEnergy ? + [ energy = 1 ] + [ performance = 3 ] + keyboardLight : ComponentWithEnergy ? + [ energy = 2 ] + [ performance = -1 ] + [ keyboard ] + cpu : ComponentWithEnergy + [ energy = 10 ] + [ performance = 15 ] + extra_cpu : ComponentWithEnergy ? + [ energy = 2 ] + [ performance = 20 ] + or location : Component ? + [ performance = 0 ] + gps : ComponentWithEnergy + [ energy = 5 ] + [ performance = -1 ] + wifitriangulation : ComponentWithEnergy + [ energy = 10 ] + [ performance = -2 ] + software : Component + [ performance = 0 ] + browser : Component ? + [ performance = -1 ] + [ extra_cpu ] + mediaplayer : Component ? + [ performance = -2 ] + total_performance : integer = sum Component.performance + total_energy : integer = sum ComponentWithEnergy.energy + +aPhone : androidPhone + [ mediaplayer ] + +<< min aPhone.total_energy >> +<< max aPhone.total_performance >> diff --git a/examples/AndroidSampleMoo_3.cfr b/examples/AndroidSampleMoo_3.cfr new file mode 100644 index 00000000..9e4dc2fd --- /dev/null +++ b/examples/AndroidSampleMoo_3.cfr @@ -0,0 +1,42 @@ +abstract Feature + performance : integer + energy : integer + +abstract SecurityFeature : Feature + security : integer + +abstract MobilePhone + or Connectivity : Feature + [ this.performance = 0] + [ this.energy = 0] + Bluetooth : Feature + [ this.performance = 9] + [ this.energy = 10 ] + USB : Feature + [ this.performance = 15] + [ this.energy = 7 ] + Wifi : Feature + [ this.performance = 22] + [ this.energy = 17 ] + PasswordProtection : SecurityFeature ? + [ this.security = 5] + [ this.performance = 1] + [ this.energy = 2 ] + FingerprintProtection : SecurityFeature ? + [ this.security = 5] + [ this.performance = 2] + [ this.energy = 4 ] + total_performance : integer + [ total_performance = sum Feature.performance ] + total_energy : integer + [ total_energy = sum Feature.energy ] + total_security : integer + [ total_security = sum SecurityFeature.security ] + +MyPhone : MobilePhone + [ Connectivity.Bluetooth && !USB ] + +<< min MyPhone.total_energy >> +<< max MyPhone.total_performance >> +<< max MyPhone.total_security >> + diff --git a/examples/AndroidSampleMoo_4.cfr b/examples/AndroidSampleMoo_4.cfr new file mode 100644 index 00000000..d0f071e5 --- /dev/null +++ b/examples/AndroidSampleMoo_4.cfr @@ -0,0 +1,50 @@ +abstract Feature + performance : integer + energy : integer + mass: integer + +abstract SecurityFeature : Feature + security : integer + +abstract MobilePhone + or Connectivity : Feature + [ this.performance = 0] + [ this.energy = 0] + [ this.mass = 0] + Bluetooth : Feature + [ this.performance = 9] + [ this.energy = 10 ] + [ this.mass = 2] + USB : Feature + [ this.performance = 10] + [ this.energy = 5 ] + [ this.mass = 3] + Wifi : Feature + [ this.performance = 22] + [ this.energy = 17 ] + [ this.mass = 6] + PasswordProtection : SecurityFeature ? + [ this.security = 5] + [ this.performance = 1] + [ this.energy = 2 ] + [ this.mass = 0] + FingerprintProtection : SecurityFeature ? + [ this.mass = 0] + [ this.security = 5] + [ this.performance = 2] + [ this.energy = 4 ] + total_performance : integer + [ total_performance = sum Feature.performance ] + total_energy : integer + [ total_energy = sum Feature.energy ] + total_security : integer + [ total_security = sum SecurityFeature.security ] + total_mass : integer + [ total_mass = sum Feature.mass ] + +MyPhone : MobilePhone + +<< min MyPhone.total_energy >> +<< max MyPhone.total_performance >> +<< max MyPhone.total_security >> +<< min MyPhone.total_mass >> \ No newline at end of file diff --git a/examples/AndroidSampleMoo_4_existing_product_spec.cfr b/examples/AndroidSampleMoo_4_existing_product_spec.cfr new file mode 100644 index 00000000..e0126932 --- /dev/null +++ b/examples/AndroidSampleMoo_4_existing_product_spec.cfr @@ -0,0 +1,50 @@ +abstract Feature + performance : integer + energy : integer + mass: integer + +abstract SecurityFeature : Feature + security : integer + +abstract MobilePhone + or Connectivity : Feature + [ this.performance = 0] + [ this.energy = 0] + [ this.mass = 0] + Bluetooth : Feature + [ this.performance = 9] + [ this.energy = 10 ] + [ this.mass = 2] + USB : Feature + [ this.performance = 10] + [ this.energy = 5 ] + [ this.mass = 3] + Wifi : Feature + [ this.performance = 22] + [ this.energy = 17 ] + [ this.mass = 6] + PasswordProtection : SecurityFeature ? + [ this.security = 5] + [ this.performance = 1] + [ this.energy = 2 ] + [ this.mass = 0] + FingerprintProtection : SecurityFeature ? + [ this.mass = 0] + [ this.security = 5] + [ this.performance = 2] + [ this.energy = 4 ] + total_performance : integer + [ total_performance = sum Feature.performance ] + total_energy : integer + [ total_energy = sum Feature.energy ] + total_security : integer + [ total_security = sum SecurityFeature.security ] + total_mass : integer + [ total_mass = sum Feature.mass ] + +MyPhone : MobilePhone + [ no Bluetooth + USB + Wifi + no PasswordProtection + FingerprintProtection] diff --git a/examples/AndroidSampleMoo_5.cfr b/examples/AndroidSampleMoo_5.cfr new file mode 100644 index 00000000..7d3872bb --- /dev/null +++ b/examples/AndroidSampleMoo_5.cfr @@ -0,0 +1,60 @@ +abstract Feature + performance : integer + energy : integer + mass: integer + cost: integer + +abstract SecurityFeature : Feature + security : integer + +abstract MobilePhone + or Connectivity : Feature + [ this.performance = 0] + [ this.energy = 0] + [ this.mass = 0] + [ this.cost = 12] + Bluetooth : Feature + [ this.performance = 9] + [ this.energy = 10 ] + [ this.mass = 2] + [ this.cost = 3] + USB : Feature + [ this.performance = 10] + [ this.energy = 5 ] + [ this.mass = 3] + [ this.cost = 5] + Wifi : Feature + [ this.performance = 22] + [ this.energy = 17 ] + [ this.mass = 6] + [ this.cost = 5] + PasswordProtection : SecurityFeature ? + [ this.security = 5] + [ this.performance = 1] + [ this.energy = 2 ] + [ this.mass = 0] + [ this.cost = 2] + FingerprintProtection : SecurityFeature ? + [ this.mass = 0] + [ this.security = 5] + [ this.performance = 3] + [ this.energy = 1 ] + [ this.cost = 13] + total_performance : integer + [ total_performance = sum Feature.performance ] + total_energy : integer + [ total_energy = sum Feature.energy ] + total_security : integer + [ total_security = sum SecurityFeature.security ] + total_mass : integer + [ total_mass = sum Feature.mass ] + total_cost : integer + [ total_cost = sum Feature.cost ] + +MyPhone : MobilePhone + +<< min MyPhone.total_energy >> +<< max MyPhone.total_performance >> +<< max MyPhone.total_security >> +<< min MyPhone.total_mass >> +<< min MyPhone.total_cost >> \ No newline at end of file diff --git a/examples/ClaferMooVizPaperExample.cfr b/examples/ClaferMooVizPaperExample.cfr new file mode 100644 index 00000000..296bfb73 --- /dev/null +++ b/examples/ClaferMooVizPaperExample.cfr @@ -0,0 +1,82 @@ +abstract Feature + productivity : integer + cost : integer + batterylife : integer + security : integer + +abstract MobilePhone + Connectivity : Feature + [ batterylife = -12] + [ productivity = 14] + [ security = 43] + [ cost = 101] + xor Bluetooth : Feature + [ batterylife = 0] + [ productivity = 0] + [ security = 0] + [ cost = 0] + Bluetooth20EDR : Feature + [ batterylife = -4] + [ productivity = 1] + [ security = -15] + [ cost = 1] + Bluetooth21EDR : Feature + [ batterylife = -2] + [ productivity = 4] + [ security = -10] + [ cost = 1] + Bluetooth40 : Feature + [ batterylife = -1] + [ productivity = 16] + [ security = -2] + [ cost = 2] + GSM : Feature + [ batterylife = -2] + [ productivity = 2] + [ security = -10] + [ cost = 1] + LTE : Feature ? + [ batterylife = -1] + [ productivity = 16] + [ security = -3] + [ cost = 3] + WiFi: Feature ? + [ batterylife = -10] + [ productivity = 20] + [ security = -15] + [ cost = 20] + USB : Feature ? + [ batterylife = 10] + [ productivity = 20] + [ security = 0] + [ cost = 3] + xor Battery : Feature + [ batterylife = 0] + [ productivity = 0] + [ security = 0] + [ cost = 0] + LiBattery1150 : Feature + [ batterylife = 60] + [ productivity = 0] + [ security = 0] + [ cost = 10] + LiBattery1400 : Feature + [ batterylife = 70] + [ productivity = 0] + [ security = 0] + [ cost = 15] + total_productivity : integer + [ total_productivity = sum Feature.productivity ] + total_batterylife : integer + [ total_batterylife = sum Feature.batterylife ] + total_security : integer + [ total_security = sum Feature.security ] + total_cost : integer + [ total_cost = sum Feature.cost ] + +MyPhone : MobilePhone + +<< max MyPhone.total_batterylife >> +<< max MyPhone.total_productivity >> +<< max MyPhone.total_security >> +<< min MyPhone.total_cost >> \ No newline at end of file diff --git a/examples/ClaferMooVizPaperExample_Downgraded_Front.cfr b/examples/ClaferMooVizPaperExample_Downgraded_Front.cfr new file mode 100644 index 00000000..2c7cf9c6 --- /dev/null +++ b/examples/ClaferMooVizPaperExample_Downgraded_Front.cfr @@ -0,0 +1,83 @@ +abstract Feature + productivity : integer + cost : integer + batterylife : integer + security : integer + +abstract MobilePhone + Connectivity : Feature + [this.batterylife = -12] + [this.productivity = 14] + [this.security = 43] + [this.cost = 101] + xor Bluetooth : Feature + [this.batterylife = 0] + [this.productivity = 0] + [this.security = 0] + [this.cost = 0] + Bluetooth20EDR : Feature + [this.batterylife = -4] + [this.productivity = 1] + [this.security = -15] + [this.cost = 1] + Bluetooth21EDR : Feature + [this.batterylife = -2] + [this.productivity = 4] + [this.security = -10] + [this.cost = 1] + Bluetooth40 : Feature + [this.batterylife = -1] + [this.productivity = 16] + [this.security = -2] + [this.cost = 2] + GSM : Feature + [this.batterylife = -2] + [this.productivity = 2] + [this.security = -10] + [this.cost = 1] + LTE : Feature ? + [this.batterylife = -1] + [this.productivity = 16] + [this.security = -3] + [this.cost = 3] + WiFi: Feature ? + [this.batterylife = -10] + [this.productivity = 20] + [this.security = -15] + [this.cost = 20] + USB : Feature ? + [this.batterylife = 10] + [this.productivity = 20] + [this.security = 0] + [this.cost = 3] + xor Battery : Feature + [this.batterylife = 0] + [this.productivity = 0] + [this.security = 0] + [this.cost = 0] + LiBattery1150 : Feature + [this.batterylife = 60] + [this.productivity = 0] + [this.security = 0] + [this.cost = 10] + LiBattery1400 : Feature + [this.batterylife = 70] + [this.productivity = 0] + [this.security = 0] + [this.cost = 15] + total_productivity : integer + [ total_productivity = sum Feature.productivity ] + total_batterylife : integer + [ total_batterylife = sum Feature.batterylife ] + total_security : integer + [ total_security = sum Feature.security ] + total_cost : integer + [ total_cost = sum Feature.cost ] + +MyPhone : MobilePhone + [!LTE] + +<< max MyPhone.total_batterylife >> +<< max MyPhone.total_productivity >> +<< max MyPhone.total_security >> +<< min MyPhone.total_cost >> \ No newline at end of file diff --git a/examples/ClaferMooVizPaperExample_Existing1.cfr b/examples/ClaferMooVizPaperExample_Existing1.cfr new file mode 100644 index 00000000..71440225 --- /dev/null +++ b/examples/ClaferMooVizPaperExample_Existing1.cfr @@ -0,0 +1,83 @@ +abstract Feature + productivity : integer + cost : integer + batterylife : integer + security : integer + +abstract MobilePhone + Connectivity : Feature + [this.batterylife = -12] + [this.productivity = 14] + [this.security = 43] + [this.cost = 101] + xor Bluetooth : Feature + [this.batterylife = 0] + [this.productivity = 0] + [this.security = 0] + [this.cost = 0] + Bluetooth20EDR : Feature + [this.batterylife = -4] + [this.productivity = 1] + [this.security = -15] + [this.cost = 1] + Bluetooth21EDR : Feature + [this.batterylife = -2] + [this.productivity = 4] + [this.security = -10] + [this.cost = 1] + Bluetooth40 : Feature + [this.batterylife = -1] + [this.productivity = 16] + [this.security = -2] + [this.cost = 2] + GSM : Feature + [this.batterylife = -2] + [this.productivity = 2] + [this.security = -10] + [this.cost = 1] + LTE : Feature ? + [this.batterylife = -1] + [this.productivity = 16] + [this.security = -3] + [this.cost = 3] + WiFi: Feature ? + [this.batterylife = -10] + [this.productivity = 20] + [this.security = -15] + [this.cost = 20] + USB : Feature ? + [this.batterylife = 10] + [this.productivity = 20] + [this.security = 0] + [this.cost = 3] + xor Battery : Feature + [this.batterylife = 0] + [this.productivity = 0] + [this.security = 0] + [this.cost = 0] + LiBattery1150 : Feature + [this.batterylife = 60] + [this.productivity = 0] + [this.security = 0] + [this.cost = 10] + LiBattery1400 : Feature + [this.batterylife = 70] + [this.productivity = 0] + [this.security = 0] + [this.cost = 15] + total_productivity : integer + [ total_productivity = sum Feature.productivity ] + total_batterylife : integer + [ total_batterylife = sum Feature.batterylife ] + total_security : integer + [ total_security = sum Feature.security ] + total_cost : integer + [ total_cost = sum Feature.cost ] + +MyPhone : MobilePhone + [Bluetooth20EDR && LiBattery1150 && !USB && !WiFi && !LTE] + +<< max MyPhone.total_batterylife >> +<< max MyPhone.total_productivity >> +<< max MyPhone.total_security >> +<< min MyPhone.total_cost >> \ No newline at end of file diff --git a/examples/Non-AFM.cfr b/examples/Non-AFM.cfr new file mode 100644 index 00000000..5bfe364b --- /dev/null +++ b/examples/Non-AFM.cfr @@ -0,0 +1,30 @@ +abstract Feature + cost: integer + +abstract ComfortFeature : Feature + comfort: integer + +abstract FuelFeature : ComfortFeature + fuel: integer + +abstract Car + ABS : Feature ? + [this.cost = 2] + CC : FuelFeature 2..3 // just to make it non-afm + [this.fuel = 1] + [this.comfort = 3] + [this.cost = 4] + ACC : FuelFeature ? + [this.fuel = 2] + [this.comfort = 6] + [this.cost = 3] + + total_cost : integer = sum Feature.cost + total_comfort : integer = sum ComfortFeature.cost + total_fuel : integer = sum FuelFeature.cost + +aCar : Car + +<< min aCar.total_cost >> +<< min aCar.total_fuel >> +<< max aCar.total_comfort >> \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..1f1a6b0c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +# Examples + +Originally *Talk_Samples* from https://github.com/gsdlab/ClaferMooVisualizer/tree/master/Talk_Samples diff --git a/examples/sysml-samples/sysmlproperty0.cfr b/examples/sysml-samples/sysmlproperty0.cfr new file mode 100644 index 00000000..1d0b1f00 --- /dev/null +++ b/examples/sysml-samples/sysmlproperty0.cfr @@ -0,0 +1,5 @@ +abstract SysmlProperty + +abstract Part: SysmlProperty + +myClafer: Part diff --git a/examples/sysml-samples/sysmlproperty1.cfr b/examples/sysml-samples/sysmlproperty1.cfr new file mode 100644 index 00000000..910cd904 --- /dev/null +++ b/examples/sysml-samples/sysmlproperty1.cfr @@ -0,0 +1,5 @@ +abstract SysmlProperty + +abstract Action: SysmlProperty + +myClafer: Action diff --git a/examples/sysml-samples/sysmlproperty2.cfr b/examples/sysml-samples/sysmlproperty2.cfr new file mode 100644 index 00000000..1305f441 --- /dev/null +++ b/examples/sysml-samples/sysmlproperty2.cfr @@ -0,0 +1,8 @@ +abstract SysmlProperty + +abstract Part: SysmlProperty + +abstract Action: SysmlProperty + +myClafer: Part + myAction: Action diff --git a/examples/test1.cfr b/examples/test1.cfr new file mode 100644 index 00000000..fd64653c --- /dev/null +++ b/examples/test1.cfr @@ -0,0 +1,22 @@ +abstract IMeasurable + footprint : integer + +abstract LinkedList + xor Abstract1 : IMeasurable + [ this.footprint = 0] + Option1 : IMeasurable + [ this.footprint = 1] + Option2 : IMeasurable + [ this.footprint = 2] + Option3 : IMeasurable + [ this.footprint = 1] + Option4 : IMeasurable + [ this.footprint = 1] + total_footprint : integer + [ total_footprint = Option1.footprint + Option2.footprint + Option3.footprint + Option4.footprint] + +simpleConfig : LinkedList + +<< min simpleConfig.total_footprint >> + +//Mandatory Features all configurations will have: Base AbstractElement AbstractIterator diff --git a/pom.xml b/pom.xml index 465dc443..9f34d6ac 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,11 @@ 4.11 test + + org.tomlj + tomlj + 1.1.0 + diff --git a/src/main/java/org/clafer/ast/analysis/PartialIntAnalyzer.java b/src/main/java/org/clafer/ast/analysis/PartialIntAnalyzer.java index 877cc0a6..2ae4c3e4 100644 --- a/src/main/java/org/clafer/ast/analysis/PartialIntAnalyzer.java +++ b/src/main/java/org/clafer/ast/analysis/PartialIntAnalyzer.java @@ -184,6 +184,10 @@ private Map partialInts(Oracle oracle) { AstRef ref = AstUtil.getInheritedRef(clafer); if (ref != null) { Path[][] paths = pathsToClafers.get(clafer); + // NULL_DEREFERENCE + if (paths == null){ + return partialInts; + } Domain[] domains = new Domain[paths.length]; for (int i = 0; i < paths.length; i++) { Domain domain = Domains.EmptyDomain; diff --git a/src/main/java/org/clafer/cli/Main.java b/src/main/java/org/clafer/cli/Main.java index d2d54628..4a7c8f4e 100644 --- a/src/main/java/org/clafer/cli/Main.java +++ b/src/main/java/org/clafer/cli/Main.java @@ -20,16 +20,20 @@ public static void main(String[] args) throws Exception { accepts( "file", "Input file in .cfr or .js format" ).withRequiredArg().ofType( File.class ) .describedAs( "Clafer model file (.cfr) or Clafer Javascript file (.js)." ); accepts( "help", "Show help.").forHelp(); + accepts( "config", "Config file in .toml format" ).withRequiredArg().ofType( File.class ); accepts( "maxint", "Specify maximum integer value." ).withRequiredArg().ofType( Integer.class ); accepts( "minint", "Specify minimum integer value." ).withRequiredArg().ofType( Integer.class ); accepts( "moo", "Run in multi-objective optimization mode." ); accepts( "n", "Specify the maximum number of instances." ).withRequiredArg().ofType( Integer.class ); accepts( "noprint", "Don't print the instances to the console or a file"); accepts( "output", "Output instances to the given file." ).withRequiredArg().ofType( File.class ).describedAs( "text file" ); + accepts( "plantuml", "Print the clafer model as PlantUML" ); + accepts( "plantuml-config", "TOML configuration file for Clafer->PlantUML compiler" ).withRequiredArg().ofType( File.class ).describedAs("toml file"); accepts( "prettify", "Use simple and pretty output format (not formal)." ); accepts( "repl", "Run in REPL (interactive) mode." ); accepts( "scope", "Override the default global scope value." ).withRequiredArg().ofType( Integer.class ); accepts( "search", "PreferSmallerInstances/PreferLargerInstances/Random" ).withRequiredArg().ofType( ClaferSearchStrategy.class ); + accepts( "sysml", "Print the instances as SysMLv2" ); accepts( "time", "Time how long it takes to find all instances (and print if it is turned on"); accepts( "v", "Run in validation mode; checks all assertions." ); accepts( "version", "Display the tool version" ); diff --git a/src/main/java/org/clafer/cli/Normal.java b/src/main/java/org/clafer/cli/Normal.java index 5fafa896..ff14cf5c 100644 --- a/src/main/java/org/clafer/cli/Normal.java +++ b/src/main/java/org/clafer/cli/Normal.java @@ -12,17 +12,29 @@ import org.clafer.javascript.JavascriptFile; import org.clafer.objective.Objective; import org.clafer.scope.Scope; +import org.clafer.ast.AstModel; +import org.plantuml.ast.PlantumlProgram; +import org.plantuml.compiler.AstPlantumlCompiler; +import org.plantuml.pprinter.PlantumlPrinter; +import org.sysml.ast.SysmlProperty; +import org.sysml.ast.SysmlPropertyDef; +import org.sysml.compiler.AstSysmlCompiler; +import org.sysml.pprinter.SysmlPrinter; public class Normal { // Running the model itself(instantiating or optimizing) public static void runNormal(JavascriptFile javascriptFile, OptionSet options, PrintStream outStream) throws Exception { + //do this first to cut irrelevant optimizing message + boolean plantuml = options.has("plantuml"); Objective[] objectives = javascriptFile.getObjectives(); - if (objectives.length == 0) - System.out.println("Instantiating..."); - else - System.out.println("Optimizing..."); + if (!plantuml){ + if (objectives.length == 0) + System.out.println("Instantiating..."); + else + System.out.println("Optimizing..."); + } // handle scopes Scope scope = Utils.resolveScopes(javascriptFile, options); @@ -39,9 +51,17 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, int index = 0; // instance id boolean prettify = options.has("prettify"); + boolean sysml = options.has("sysml"); boolean printOff = options.has("noprint"); boolean dataTackingOn = options.has("dataFile"); boolean timeOn = options.has("time"); + + // check for conflicting options + if (plantuml && sysml) { + System.err.println("Bad CLI config: both plantuml and sysml are selected"); + return; + } + File dataFile; PrintStream dataStream = null; if (dataTackingOn) { @@ -49,6 +69,21 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, dataStream = new PrintStream(dataFile); } + if (plantuml) { + File plantumlConfigFile = (File) options.valueOf("plantuml-config"); + AstModel top = javascriptFile.getModel(); + AstPlantumlCompiler compiler = AstPlantumlCompiler + .AstPlantumlCompilerBuilder + .buildFromToml(plantumlConfigFile); + PlantumlProgram prog = compiler.compile(top); + PlantumlPrinter pprinter = new PlantumlPrinter(outStream, plantumlConfigFile); + pprinter.visit(prog, ""); + if (dataStream != null){ + dataStream.close(); + } + return; + } + double elapsedTime; long startTime = System.nanoTime(); @@ -71,28 +106,50 @@ public static void runNormal(JavascriptFile javascriptFile, OptionSet options, if (printOff) { ++index; } else { - outStream.println("=== Instance " + (++index) + " Begin ===\n"); - InstanceModel instance = solver.instance(); - if (prettify) - instance.print(outStream); - else - for (InstanceClafer c : instance.getTopClafers()) - Utils.printClafer(c, outStream); - outStream.println("\n--- Instance " + (index) + " End ---\n"); + if (sysml) { + outStream.append("package Instance" + (++index) + "{\n"); + outStream.append(" import ScalarValues::*;\n"); + AstModel top = javascriptFile.getModel(); + SysmlPrinter pprinter = new SysmlPrinter(outStream); + AstSysmlCompiler compiler = new AstSysmlCompiler(); + SysmlPropertyDef[] models = compiler.compile(top, top); + for (SysmlPropertyDef model: models){ + pprinter.visit(model, " "); + } + + InstanceModel instance = solver.instance(); + instance.printSysml(outStream, " "); + outStream.append("}\n"); + } else { + outStream.println("=== Instance " + (++index) + " Begin ===\n"); + InstanceModel instance = solver.instance(); + if (prettify) + instance.print(outStream); + else + for (InstanceClafer c : instance.getTopClafers()) + Utils.printClafer(c, outStream); + outStream.println("\n--- Instance " + (index) + " End ---\n"); + } } } - if (timeOn) { - elapsedTime = (double) (System.nanoTime() - startTime) / 1000000000; - if (objectives.length == 0) - System.out.println("Generated " + index + " instance(s) within the scope in " + elapsedTime + " seconds\n"); - else - System.out.println("Generated " + (n == -1 ? "all " : "") + index + " optimal instance(s) within the scope in " + elapsedTime + " secondse\n"); - } else { - if (objectives.length == 0) - System.out.println("Generated " + index + " instance(s) within the scope\n"); - else - System.out.println("Generated " + (n == -1 ? "all " : "") + index + " optimal instance(s) within the scope\n"); + if (!sysml) { + if (timeOn) { + elapsedTime = (double) (System.nanoTime() - startTime) / 1000000000; + if (objectives.length == 0) + System.out.println("Generated " + index + " instance(s) within the scope in " + elapsedTime + " seconds\n"); + else + System.out.println("Generated " + (n == -1 ? "all " : "") + index + " optimal instance(s) within the scope in " + elapsedTime + " secondse\n"); + } else { + if (objectives.length == 0) + System.out.println("Generated " + index + " instance(s) within the scope\n"); + else + System.out.println("Generated " + (n == -1 ? "all " : "") + index + " optimal instance(s) within the scope\n"); + } } + // make sure to close this resource + if (dataStream != null){ + dataStream.close(); + } } } diff --git a/src/main/java/org/clafer/instance/InstanceModel.java b/src/main/java/org/clafer/instance/InstanceModel.java index 906b00ef..fd97bcad 100644 --- a/src/main/java/org/clafer/instance/InstanceModel.java +++ b/src/main/java/org/clafer/instance/InstanceModel.java @@ -4,8 +4,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; + import org.clafer.ast.AstConcreteClafer; import org.clafer.common.Check; +import org.sysml.ast.SysmlProperty; +import org.sysml.compiler.InstanceSysmlCompiler; +import org.sysml.pprinter.SysmlPrinter; /** * @@ -49,6 +53,28 @@ public InstanceClafer getTopClafer(AstConcreteClafer type) { return typedTopClafer; } + /** + * Print solution as SysMLv2 + * + * This is the start of exploring an automated product derivation tool + * and the requirements around inferring a system model from clafer are + * still tbd + * + * + */ + public void printSysml(Appendable out, String indent) throws IOException { + + + for (InstanceClafer top : topClafers) { + SysmlPrinter pprinter = new SysmlPrinter(out); + InstanceSysmlCompiler compiler = new InstanceSysmlCompiler(); + // model can be null as the clafer model might not reference sysml concepts + Object _model = compiler.compile(top, top); + if (_model != null) + pprinter.visit((SysmlProperty) _model, indent); + } + } + /** * Print solution to stdout. * diff --git a/src/main/java/org/plantuml/ast/PlantumlConnection.java b/src/main/java/org/plantuml/ast/PlantumlConnection.java new file mode 100644 index 00000000..c1fed20d --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlConnection.java @@ -0,0 +1,61 @@ +package org.plantuml.ast; + +import java.io.IOException; + +public class PlantumlConnection implements PlantumlExpr { + private final String fromObj; + private final String toObj; + private final char fromConn; + private final char toConn; + + private final char lineChar; + + private final String label; + + public PlantumlConnection(String fromObj, String toObj, char fromConn, char toConn, String label){ + this.fromObj = fromObj; + this.toObj = toObj; + this.fromConn = fromConn; + this.toConn = toConn; + this.label = label; + this.lineChar = '-'; + } + + public PlantumlConnection(String fromObj, String toObj, char fromConn, char toConn, String label, char lineChar){ + this.fromObj = fromObj; + this.toObj = toObj; + this.fromConn = fromConn; + this.toConn = toConn; + this.label = label; + this.lineChar = lineChar; + } + + public String getFromObj(){ + return fromObj; + } + + public String getToObj(){ + return toObj; + } + + public char getToConn(){ + return toConn; + } + + public char getFromConn(){ + return fromConn; + } + + public String getLabel(){ + return label; + } + + public char getLineChar(){ + return lineChar; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/plantuml/ast/PlantumlExpr.java b/src/main/java/org/plantuml/ast/PlantumlExpr.java new file mode 100644 index 00000000..c32e48da --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlExpr.java @@ -0,0 +1,16 @@ +package org.plantuml.ast; + +import java.io.IOException; + +public interface PlantumlExpr { + /** + * Dynamic dispatch on the visitor. + * + * @param the parameter type + * @param the return type + * @param visitor the visitor + * @param a the parameter + * @return the return value + */ + B accept(PlantumlExprVisitor visitor, A a) throws IOException; +} diff --git a/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java new file mode 100644 index 00000000..115ef7f1 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlExprVisitor.java @@ -0,0 +1,21 @@ +package org.plantuml.ast; + +import java.io.IOException; + +/** + * AST Visitor + * + * We make AST visitors capable of throwing IOExecptions as it's convenient for pretty printers + * However, we could likely get rid of this throw some type of interface conversion. + */ +public interface PlantumlExprVisitor { + B visit(PlantumlProgram plantumlProgram, A a) throws IOException; + + B visit(PlantumlObject plantumlObject, A a) throws IOException; + + B visit(PlantumlPropertyGroup plantumlPropertyGroup, A a) throws IOException; + + B visit(PlantumlProperty plantumlProperty, A a) throws IOException; + + B visit(PlantumlConnection plantumlConnection, A a) throws IOException; +} diff --git a/src/main/java/org/plantuml/ast/PlantumlId.java b/src/main/java/org/plantuml/ast/PlantumlId.java new file mode 100644 index 00000000..f1be4665 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlId.java @@ -0,0 +1,10 @@ +package org.plantuml.ast; + +public interface PlantumlId { + /** + * PlantUML + * + * @return the name of the identifier + */ + String getName(); +} diff --git a/src/main/java/org/plantuml/ast/PlantumlObject.java b/src/main/java/org/plantuml/ast/PlantumlObject.java new file mode 100644 index 00000000..6559df95 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlObject.java @@ -0,0 +1,40 @@ +package org.plantuml.ast; + +import java.io.IOException; + +public class PlantumlObject implements PlantumlExpr, PlantumlId { + private final String name; + private final String alias; + private final String parent; + + private final PlantumlPropertyGroup[] propertyGroups; + + public PlantumlObject(String name, String alias, String parent, PlantumlPropertyGroup[] propertyGroups) { + this.name = name; + this.alias = alias; + this.parent = parent; + this.propertyGroups = propertyGroups; + } + + public PlantumlPropertyGroup[] getPropertyGroups() { + return propertyGroups; + } + + @Override + public String getName() { + return name; + } + + public String getAlias() { + return alias; + } + + public String getParent() { + return parent; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/plantuml/ast/PlantumlProgram.java b/src/main/java/org/plantuml/ast/PlantumlProgram.java new file mode 100644 index 00000000..0cfad97d --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlProgram.java @@ -0,0 +1,41 @@ +package org.plantuml.ast; + +import org.sysml.ast.SysmlExpr; +import org.sysml.ast.SysmlExprVisitor; + +import java.io.IOException; + +/** + * Main PlantUML Program Element + * + * This is likely quite wrong. For our use case, we consider a PlantUML program as a collection + * of objects and connections. + */ +public class PlantumlProgram implements PlantumlExpr { + private PlantumlObject[] objects; + private PlantumlConnection[] connections; + + public PlantumlProgram() { + this.objects = new PlantumlObject[0]; + this.connections = new PlantumlConnection[0]; + } + + public PlantumlProgram(PlantumlObject[] objects, PlantumlConnection[] connections) { + this.objects = objects; + this.connections = connections; + + } + + public PlantumlObject[] getObjects(){ + return objects; + } + + public PlantumlConnection[] getConnections(){ + return connections; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/plantuml/ast/PlantumlProperty.java b/src/main/java/org/plantuml/ast/PlantumlProperty.java new file mode 100644 index 00000000..4c741d5d --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlProperty.java @@ -0,0 +1,29 @@ +package org.plantuml.ast; + +import java.io.IOException; + +public class PlantumlProperty implements PlantumlExpr { + private final String prop; + + public PlantumlProperty(String prop) { + // NOTE: do you want to make this configurable though the TOML file? That might be a stretch... + prop = prop.replace("c0_", ""); // Remove "c0_" prefix (instances) + prop = prop.replace("this . ", ""); // Remove "this ." prefix (self attributes) + prop = prop.replace(" . ref", ""); // Remove "ref ." prefix (type defs) + prop = prop.replace(" ", " "); // Only single space + prop = prop.replace(" . ", "."); // Remove spaces between names + prop = prop.replace("[", ""); // Remove square brackets + prop = prop.replace("]", ""); // Remove square brackets + prop = prop.replace("parent.", ""); // Remove `parent.` from the fully qualified name + this.prop = prop; + } + + public String getProp(){ + return prop; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/plantuml/ast/PlantumlPropertyGroup.java b/src/main/java/org/plantuml/ast/PlantumlPropertyGroup.java new file mode 100644 index 00000000..87b8fe47 --- /dev/null +++ b/src/main/java/org/plantuml/ast/PlantumlPropertyGroup.java @@ -0,0 +1,28 @@ +package org.plantuml.ast; + +import java.io.IOException; + +public class PlantumlPropertyGroup implements PlantumlId, PlantumlExpr { + + private final String name; + private PlantumlProperty[] properties; + + public PlantumlPropertyGroup(String name, PlantumlProperty[] properties) { + this.properties = properties; + this.name = name; + } + + public PlantumlProperty[] getProperties() { + return this.properties; + } + + @Override + public String getName() { + return name; + } + + @Override + public B accept(PlantumlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java new file mode 100644 index 00000000..fa141a77 --- /dev/null +++ b/src/main/java/org/plantuml/compiler/AstPlantumlCompiler.java @@ -0,0 +1,448 @@ +package org.plantuml.compiler; + +import org.clafer.ast.*; +import org.plantuml.ast.*; +import org.plantuml.compiler.PlantumlCompilerUtils; +import org.tomlj.Toml; +import org.tomlj.TomlParseResult; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Clafer AST to PlantUML + *

+ * Note that this compilation doesn't require instances, so we don't need to run the solver + * to compile. + *

+ * TODO: this should be refactored to cut down the code re-use. + */ +public class AstPlantumlCompiler { + private final boolean includeConstraints; + private final boolean includeSuperClafersConnections; + private final boolean includeSuperClafersComponents; + private final int includeLevels; + + private final List clafersBlacklist; + + public AstPlantumlCompiler(AstPlantumlCompilerBuilder builder) { + this.includeConstraints = builder.includeConstraints; + this.includeSuperClafersConnections = builder.includeSuperClafersConnections; + this.includeSuperClafersComponents = builder.includeSuperClafersComponents; + this.includeLevels = builder.includeLevels; + this.clafersBlacklist = builder.clafersBlacklist; + } + + /** + * Check that a clafer name matches an entry in the blacklist + * + * @param name + * @return whether name should be blacklisted + */ + boolean checkBlacklist(String name) { + for (String regex : this.clafersBlacklist) { + Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(name); + boolean matchFound = matcher.find(); + if (matchFound) return true; + } + return false; + } + + /** + * Check that a clafer is allowed + *

+ * Note that we ignore all names beginning with # because they are + * generated by the clafer compiler and implicit to the user's code. + * + * @param name + * @return whether name should be compiled + */ + private boolean checkClaferName(String name) { + return !checkBlacklist(name) && !name.startsWith("#"); + } + + /** + * collect all concrete clafers + * + * @param concreteClafers concreteClafers held in a claferModel + * @return ArrayList of all nested clafers (abstract included) + */ + private ArrayList getConcreteObjects(List concreteClafers, int level) { + ArrayList objs = new ArrayList(); + + for (AstConcreteClafer ast : concreteClafers) { + if (ast.getRef() != null) continue; + + // build the property groups + ArrayList pgs = new ArrayList(); + + ArrayList constrs = new ArrayList(); + for (AstConstraint constr : ast.getConstraints()) { + constrs.add(new PlantumlProperty(constr.toString())); + } + if (constrs.size() > 0 && this.includeConstraints) { + pgs.add(new PlantumlPropertyGroup("Constraints", constrs.toArray(new PlantumlProperty[0]))); + } + + // Display inheritance as a style notation + // NOTE: this could be made optional + AstClafer superClafer = ast.getSuperClafer(); + String scName = ""; + if (superClafer != null && !PlantumlCompilerUtils.getPropertyId(superClafer.getName()).startsWith("#") && this.includeSuperClafersComponents) { + scName = PlantumlCompilerUtils.getPropertyId(superClafer.getName()); + } + + // create an object and add it + PlantumlObject obj = new PlantumlObject( + PlantumlCompilerUtils.getPropertyId(ast.getName()), + PlantumlCompilerUtils.getPropertyAlias(ast.getName()), + scName, + pgs.toArray(new PlantumlPropertyGroup[0]) + ); + + if (checkClaferName(obj.getName())) objs.add(obj); + + // add all of its children + // TODO: check for collisions? + if (level < this.includeLevels) objs.addAll(getConcreteObjects(ast.getChildren(), level + 1)); + } + return objs; + } + + /** + * collect all abstract clafers (give them an abstract attribute) + * + * @param abstractClafers abstractClafers held in a claferModel + * @return ArrayList of all nested clafers (concrete included) + */ + private ArrayList getAbstractObjects(List abstractClafers, int level) { + ArrayList objs = new ArrayList(); + + for (AstAbstractClafer ast : abstractClafers) { + if (ast.getRef() != null) { + continue; + } + + // build out the constraints and attributes property groups + ArrayList pgs = new ArrayList(); + + ArrayList constrs = new ArrayList(); + for (AstConstraint constr : ast.getConstraints()) { + constrs.add(new PlantumlProperty(constr.toString())); + } + + ArrayList refs = new ArrayList(); + for (AstConcreteClafer clafer : ast.getChildren()) { + AstRef ref = clafer.getRef(); + if (ref != null) { + refs.add(new PlantumlProperty(ref.toString())); + } + } + + if (refs.size() > 0) { + pgs.add(new PlantumlPropertyGroup("Attributes", refs.toArray(new PlantumlProperty[0]))); + } + + if (constrs.size() > 0 && this.includeConstraints) { + pgs.add(new PlantumlPropertyGroup("Constraints", constrs.toArray(new PlantumlProperty[0]))); + } + + // create an object and add it + PlantumlObject obj = new PlantumlObject( + PlantumlCompilerUtils.getPropertyId(ast.getName()), + PlantumlCompilerUtils.getPropertyAlias(ast.getName()), + null, + pgs.toArray(new PlantumlPropertyGroup[0]) + ); + + if (checkClaferName(obj.getName())) objs.add(obj); + + // add all of its children + // TODO: check for collisions? + if (level < this.includeLevels) objs.addAll(getAbstractObjects(ast.getAbstractChildren(), level + 1)); + if (level < this.includeLevels) objs.addAll(getConcreteObjects(ast.getChildren(), level + 1)); + } + return objs; + } + + /** + * top-level object collector + * + * @param model the root clafer model + * @return ArrayList of all clafers (abstract and concrete) suitable for PlantUML objects + */ + private ArrayList getObjects(AstModel model) { + ArrayList objs = getAbstractObjects(model.getAbstracts(), 0); + objs.addAll(getConcreteObjects(model.getChildren(), 0)); + return objs; + } + + private char getEndArrowhead(boolean hasGroupCard, Card card, Card groupCard) { + char toConn = '*'; + if (hasGroupCard) toConn = '-'; + else { + // No Alternative/OR + if (card.toString().equals("0..1")) toConn = 'o'; + else if (card.toString().equals("1")) toConn = '*'; + else { + if (card.toString().startsWith("0")) toConn = 'o'; + } + } + return toConn; + } + + private char getStartArrowhead(boolean hasGroupCard, Card groupCard) { + char fromConn = '-'; + if (hasGroupCard) { + if (groupCard.toString().equals("1")) { + // This is an OR (exactly one) + fromConn = '+'; + } else if (groupCard.toString().equals("1..*")) { + // This is an Alternative (1 or more) + fromConn = ')'; + } + } + return fromConn; + } + + private String getLabel(boolean hasGroupCard, Card card) { + String label = ""; + if (!hasGroupCard) { + // No Alternative/OR + if (!card.toString().equals("0..1") && !card.toString().equals("1")) { + label = card.toString(); + } + } + return label; + } + + private ArrayList getConcreteConnections(List concreteClafers, int level) { + ArrayList connections = new ArrayList(); + + for (AstConcreteClafer ast : concreteClafers) { + if (ast.getRef() != null) { + continue; + } + String fromObj = PlantumlCompilerUtils.getPropertyId(ast.getParent().getName()); + String toObj = PlantumlCompilerUtils.getPropertyId(ast.getName()); + + Card card = ast.getCard(); + boolean hasGroupCard = ast.getParent().hasGroupCard(); + + String label = getLabel(hasGroupCard, card); + char fromConn = getStartArrowhead(hasGroupCard, ast.getParent().getGroupCard()); + char toConn = getEndArrowhead(hasGroupCard, card, ast.getParent().getGroupCard()); + + if (checkClaferName(fromObj) && checkClaferName(toObj)) { + connections.add( + new PlantumlConnection( + fromObj, + toObj, + fromConn, + toConn, + label + ) + ); + } + + if (this.includeSuperClafersConnections) { + AstClafer superClafer = ast.getSuperClafer(); + if (superClafer != null) { + String scName = PlantumlCompilerUtils.getPropertyId(superClafer.getName()); + if (checkClaferName(scName)) { + fromObj = toObj; + toObj = scName; + connections.add( + new PlantumlConnection( + fromObj, + toObj, + '.', + '>', + "", + '.' + ) + ); + } + } + } + + if (level < this.includeLevels) connections.addAll(getConcreteConnections(ast.getChildren(), level + 1)); + } + + return connections; + } + + private ArrayList getAbstractConnections(List abstractClafers, int level) { + ArrayList connections = new ArrayList(); + + for (AstAbstractClafer ast : abstractClafers) { + if (ast.getRef() != null) continue; + + // collect the connection labels + String fromObj = PlantumlCompilerUtils.getPropertyId(ast.getParent().getName()); + String toObj = PlantumlCompilerUtils.getPropertyId(ast.getName()); + String label = ""; + char toConn = '*'; + char fromConn = getStartArrowhead(ast.getParent().hasGroupCard(), ast.getParent().getGroupCard()); + + if (checkClaferName(fromObj) && checkClaferName(toObj)) { + connections.add( + new PlantumlConnection( + fromObj, + toObj, + fromConn, + toConn, + label + ) + ); + } + + if (this.includeSuperClafersConnections) { + AstClafer superClafer = ast.getSuperClafer(); + if (superClafer != null) { + String scName = PlantumlCompilerUtils.getPropertyId(superClafer.getName()); + if (!scName.startsWith("#") && !checkBlacklist(scName)) { + fromObj = toObj; + toObj = scName; + connections.add( + new PlantumlConnection( + fromObj, + toObj, + '.', + '>', + "", + '.' + ) + ); + } + } + } + + if (level < this.includeLevels) + connections.addAll(getAbstractConnections(ast.getAbstractChildren(), level + 1)); + if (level < this.includeLevels) connections.addAll(getConcreteConnections(ast.getChildren(), level + 1)); + } + + return connections; + } + + private ArrayList getConnections(AstModel model) { + ArrayList connections = getAbstractConnections(model.getAbstracts(), 0); + connections.addAll(getConcreteConnections(model.getChildren(), 0)); + return connections; + } + + public PlantumlProgram compile(AstModel model) { + ArrayList objs = getObjects(model); + ArrayList conns = getConnections(model); + + return new PlantumlProgram( + objs.toArray(new PlantumlObject[0]), conns.toArray(new PlantumlConnection[0]) + ); + } + + + /** + * builder class to configure the Compiler + * we anticipate a lot of options here, so we use a builder + */ + public static class AstPlantumlCompilerBuilder { + + private boolean includeConstraints; + private boolean includeSuperClafersConnections; + private boolean includeSuperClafersComponents; + private int includeLevels; + + private List clafersBlacklist; + + public AstPlantumlCompilerBuilder() { + this.includeConstraints = true; + this.includeSuperClafersConnections = true; + this.includeSuperClafersComponents = true; + this.includeLevels = Integer.MAX_VALUE; + this.clafersBlacklist = new ArrayList(); + } + + public AstPlantumlCompiler build() { + return new AstPlantumlCompiler(this); + } + + public AstPlantumlCompilerBuilder setIncludeConstraints(boolean includeConstraints) { + this.includeConstraints = includeConstraints; + return this; + } + + public AstPlantumlCompilerBuilder setIncludeSuperClafersConnections(boolean includeSuperClafers) { + this.includeSuperClafersConnections = includeSuperClafers; + return this; + } + + public AstPlantumlCompilerBuilder setIncludeSuperClafersComponents(boolean includeSuperClafers) { + this.includeSuperClafersComponents = includeSuperClafers; + return this; + } + + public AstPlantumlCompilerBuilder setLevels(int levels) { + this.includeLevels = levels; + return this; + } + + public AstPlantumlCompilerBuilder setClafersBlacklist(List bl) { + this.clafersBlacklist = bl; + return this; + } + + /** + * read builder from an input toml file (null returns defaults) + * + * @param tomlFile config file + * @return a compiler object + * @throws IOException + */ + static public AstPlantumlCompiler buildFromToml(File tomlFile) throws IOException { + if (tomlFile == null) { + return new AstPlantumlCompiler(new AstPlantumlCompilerBuilder()); + } else { + // TODO: Toml4J seems to do better error checking than this + Path source = Paths.get(tomlFile.toURI()); + TomlParseResult result = Toml.parse(source); + result.errors().forEach(error -> System.err.println(error.toString())); + + AstPlantumlCompilerBuilder build = new AstPlantumlCompilerBuilder(); + + String field = "include.super_clafers_connections"; + if (result.contains(field)) build.setIncludeSuperClafersConnections(Boolean.TRUE.equals(result.getBoolean(field))); + + field = "include.super_clafers_components"; + if (result.contains(field)) build.setIncludeSuperClafersComponents(Boolean.TRUE.equals(result.getBoolean(field))); + + field = "include.constraints"; + if (result.contains(field)) build.setIncludeConstraints(Boolean.TRUE.equals(result.getBoolean(field))); + + field = "include.levels"; + if (result.contains(field)) build.setLevels(Objects.requireNonNull(result.getLong(field)).intValue()); + + field = "blacklist.clafers"; + if (result.contains(field)) { + List blacklist = new ArrayList<>(); + for (Object o : result.getArrayOrEmpty(field) + .toList()) { + String s = Objects.toString(o, null); + blacklist.add(s); + } + build.setClafersBlacklist(blacklist); + } + + return build.build(); + } + } + } +} diff --git a/src/main/java/org/plantuml/compiler/PlantumlCompilerUtils.java b/src/main/java/org/plantuml/compiler/PlantumlCompilerUtils.java new file mode 100644 index 00000000..7410cd7c --- /dev/null +++ b/src/main/java/org/plantuml/compiler/PlantumlCompilerUtils.java @@ -0,0 +1,31 @@ +package org.plantuml.compiler; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PlantumlCompilerUtils { + public static String getPropertyId(String name) { + Pattern pattern = Pattern.compile("(c[0-9]+_).*", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(name); + boolean matchFound = matcher.find(); + //matcher.group() + if (matchFound) { + String idxS = matcher.group(1); + int idx = Integer.parseInt(idxS.substring(1, idxS.length() - 1)); + return name.substring(matcher.group(1).length()) + "_" + String.valueOf(idx); + } else { + return name; + } + } + + public static String getPropertyAlias(String name) { + Pattern pattern = Pattern.compile("(c[0-9]+_).*", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(name); + boolean matchFound = matcher.find(); + if (matchFound) { + return name.substring(matcher.group(1).length()); + } else { + return name; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java new file mode 100644 index 00000000..0f964251 --- /dev/null +++ b/src/main/java/org/plantuml/pprinter/PlantumlPrinter.java @@ -0,0 +1,120 @@ +package org.plantuml.pprinter; + +import org.plantuml.ast.*; +import org.tomlj.Toml; +import org.tomlj.TomlParseResult; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * PlantUML -> Text + * + * Visits the PlantUML AST and generates text output to an Appendable stream + */ +public class PlantumlPrinter implements PlantumlExprVisitor { + private final String indentBase; + private final Appendable out; + private String header; + + /** + * Initialize a printer + * @param out appendable stream + * @param tomlFile config file + * @throws IOException + */ + public PlantumlPrinter(Appendable out, File tomlFile) throws IOException { + this.out = out; + this.indentBase = " "; + this.header = ""; + if (tomlFile != null) { + // NOTE: silly that we are redoing what is done in AstPlantumlCompilerBuilder to get the header + Path source = Paths.get(tomlFile.toURI()); + TomlParseResult result = Toml.parse(source); + result.errors().forEach(error -> System.err.println(error.toString())); + String field = "include.header"; + if (result.contains(field)) { + this.header = result.getString(field); + } + } + } + + // implement the visitor + @Override + public Void visit(PlantumlProgram ast, String indent) throws IOException { + this.out.append(indent).append("@startuml").append("\n").append(this.header).append("\n"); + for (PlantumlObject obj: ast.getObjects()){ + obj.accept(this, indent + indentBase); + } + this.out.append('\n'); + for (PlantumlConnection conn: ast.getConnections()){ + conn.accept(this, indent + indentBase); + } + this.out.append(indent).append("@enduml").append("\n"); + return null; + } + + @Override + public Void visit(PlantumlObject plantumlObject, String s) throws IOException { + this.out.append(s).append("object "); + + String alias = plantumlObject.getAlias(); + String parent = plantumlObject.getParent(); + + if (alias != null) { + this.out.append('"').append(alias).append('"').append(" as "); + } + + this.out.append(plantumlObject.getName()); + + if (parent != null ) { + if (!parent.isEmpty()) { + this.out.append("<<").append(parent).append(">>"); + } + } + + if (plantumlObject.getPropertyGroups().length > 0) { + this.out.append(" {\n"); + for (PlantumlPropertyGroup grp: plantumlObject.getPropertyGroups()){ + grp.accept(this, s + indentBase); + } + this.out.append(s).append("}\n"); + } else { + this.out.append("\n"); + } + return null; + } + + @Override + public Void visit(PlantumlPropertyGroup plantumlPropertyGroup, String s) throws IOException { + this.out.append(s).append(".. ").append(plantumlPropertyGroup.getName()).append(" ..").append("\n"); + for (PlantumlProperty prop: plantumlPropertyGroup.getProperties()){ + prop.accept(this, s); + } + return null; + } + + @Override + public Void visit(PlantumlProperty plantumlProperty, String s) throws IOException { + this.out.append(s).append("* ").append(plantumlProperty.getProp()).append('\n'); + return null; + } + + @Override + public Void visit(PlantumlConnection plantumlConnection, String s) throws IOException { + this.out.append(s) + .append(plantumlConnection.getFromObj()) + .append(" ").append(plantumlConnection.getFromConn()).append(plantumlConnection.getLineChar()).append(plantumlConnection.getToConn()) + .append(" ") + .append(plantumlConnection.getToObj()); + if (plantumlConnection.getLabel().length() > 0){ + this.out.append(" ") + .append(": ") + .append(plantumlConnection.getLabel()); + } + this.out.append('\n'); + return null; + } +} diff --git a/src/main/java/org/sysml/ast/SysmlAttribute.java b/src/main/java/org/sysml/ast/SysmlAttribute.java new file mode 100644 index 00000000..3d6a7f0f --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlAttribute.java @@ -0,0 +1,27 @@ +package org.sysml.ast; + +import java.io.IOException; + +public class SysmlAttribute implements SysmlExpr, SysmlId { + private String name; + + private Object ref; + + public SysmlAttribute(String name, Object ref) { + this.name = name; + this.ref = ref; + } + + @Override + public String getName() { + return name; + } + + public String getRef() { + return ref.toString(); + } + @Override + public B accept(SysmlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/sysml/ast/SysmlBlockDefElement.java b/src/main/java/org/sysml/ast/SysmlBlockDefElement.java new file mode 100644 index 00000000..6707f189 --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlBlockDefElement.java @@ -0,0 +1,4 @@ +package org.sysml.ast; + +public interface SysmlBlockDefElement extends SysmlExpr, SysmlId { +} diff --git a/src/main/java/org/sysml/ast/SysmlBlockVisibility.java b/src/main/java/org/sysml/ast/SysmlBlockVisibility.java new file mode 100644 index 00000000..bacb52ed --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlBlockVisibility.java @@ -0,0 +1,14 @@ +package org.sysml.ast; + +/** + * Sysml Block Visibility + * ::= | ‘#’ | ‘~’ + */ +public class SysmlBlockVisibility { + public final SysmlVisibilityOption option; + + public SysmlBlockVisibility(SysmlVisibilityOption visOpt) { + this.option = visOpt; + } + +} diff --git a/src/main/java/org/sysml/ast/SysmlExpr.java b/src/main/java/org/sysml/ast/SysmlExpr.java new file mode 100644 index 00000000..2e64882c --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlExpr.java @@ -0,0 +1,16 @@ +package org.sysml.ast; + +import java.io.IOException; + +public interface SysmlExpr { + /** + * Dynamic dispatch on the visitor. + * + * @param the parameter type + * @param the return type + * @param visitor the visitor + * @param a the parameter + * @return the return value + */ + B accept(SysmlExprVisitor visitor, A a) throws IOException; +} diff --git a/src/main/java/org/sysml/ast/SysmlExprVisitor.java b/src/main/java/org/sysml/ast/SysmlExprVisitor.java new file mode 100644 index 00000000..7cb6f748 --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlExprVisitor.java @@ -0,0 +1,13 @@ +package org.sysml.ast; + +import java.io.IOException; + +public interface SysmlExprVisitor { + B visit(SysmlPackage ast, A a) throws IOException; + + B visit(SysmlProperty ast, A a) throws IOException; + + B visit(SysmlAttribute ast, A a) throws IOException; + + B visit(SysmlPropertyDef sysmlPropertyDef, A a) throws IOException; +} diff --git a/src/main/java/org/sysml/ast/SysmlId.java b/src/main/java/org/sysml/ast/SysmlId.java new file mode 100644 index 00000000..816d7dad --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlId.java @@ -0,0 +1,10 @@ +package org.sysml.ast; + +public interface SysmlId { + /** + * SysML + * + * @return the name of the identifier + */ + String getName(); +} diff --git a/src/main/java/org/sysml/ast/SysmlPackage.java b/src/main/java/org/sysml/ast/SysmlPackage.java new file mode 100644 index 00000000..d3e478ba --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlPackage.java @@ -0,0 +1,31 @@ +package org.sysml.ast; + + + +import java.io.IOException; + +/** + * TODO: build out the DiagramElement taxonomy better + */ +public class SysmlPackage implements SysmlBlockDefElement { + private final SysmlBlockDefElement[] elements; + private final String name; + + public SysmlPackage(String name, SysmlBlockDefElement[] elements){ + this.elements = elements; + this.name = name; + } + + public String getName() { + return name; + } + + public SysmlBlockDefElement[] getElements(){ + return elements; + } + + @Override + public B accept(SysmlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/sysml/ast/SysmlProperty.java b/src/main/java/org/sysml/ast/SysmlProperty.java new file mode 100644 index 00000000..5f8f9747 --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlProperty.java @@ -0,0 +1,84 @@ +package org.sysml.ast; + +import org.clafer.common.Check; + +import java.io.IOException; + +/** + * SysML Property + * + * ::= [] [‘/’] [] + * + * TODO: do the declaration + */ +public class SysmlProperty implements SysmlBlockDefElement { + + private final String name; + + private final SysmlPropertyType prop; + + private final SysmlBlockDefElement[] elements; + + private final SysmlAttribute[] annotations; + private final String[] superTypes; + + private int multiplicity; + + + + public SysmlProperty( + SysmlBlockVisibility blockVis, + SysmlPropertyType propType, + String name, + SysmlBlockDefElement[] elements, + SysmlAttribute[] annotations, + String[] superTypes, + int multiplicity + ){ + this.name = Check.notNull(name); + this.prop = propType; + this.elements = elements; + this.superTypes = superTypes; + this.multiplicity = multiplicity; + this.annotations = annotations; + } + + public SysmlProperty(SysmlBlockVisibility blockVis, SysmlPropertyType propType, String name){ + this.name = Check.notNull(name); + this.prop = propType; + this.elements = new SysmlBlockDefElement[0]; + this.superTypes = new String[0]; + this.annotations = new SysmlAttribute[0]; + this.multiplicity = 1; + } + + @Override + public String getName() { + return name; + } + + public SysmlPropertyType getPropertyType(){ + return prop; + } + + public SysmlBlockDefElement[] getElements() { + return elements; + } + + public String[] getSupers(){ + return superTypes; + } + + public int getMultiplicity(){ + return multiplicity; + } + + public SysmlAttribute[] getAnnotations(){ + return annotations; + } + + @Override + public B accept(SysmlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/sysml/ast/SysmlPropertyDef.java b/src/main/java/org/sysml/ast/SysmlPropertyDef.java new file mode 100644 index 00000000..dd61de05 --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlPropertyDef.java @@ -0,0 +1,65 @@ +package org.sysml.ast; + +import org.clafer.common.Check; + +import java.io.IOException; + +public class SysmlPropertyDef implements SysmlBlockDefElement { + private final String name; + + private final SysmlPropertyType prop; + + private final SysmlBlockDefElement[] elements; + + private final SysmlAttribute[] annotations; + private final String[] superTypes; + + public SysmlPropertyDef( + SysmlBlockVisibility blockVis, + SysmlPropertyType propType, + String name, + SysmlBlockDefElement[] elements, + SysmlAttribute[] annotations, + String[] superTypes + ){ + this.name = Check.notNull(name); + this.prop = propType; + this.elements = elements; + this.superTypes = superTypes; + this.annotations = annotations; + } + + public SysmlPropertyDef(SysmlBlockVisibility blockVis, SysmlPropertyType propType, String name){ + this.name = Check.notNull(name); + this.prop = propType; + this.elements = new SysmlBlockDefElement[0]; + this.superTypes = new String[0]; + this.annotations = new SysmlAttribute[0]; + } + + @Override + public String getName() { + return name; + } + + public SysmlPropertyType getPropertyType(){ + return prop; + } + + public SysmlBlockDefElement[] getElements() { + return elements; + } + + public String[] getSupers(){ + return superTypes; + } + + public SysmlAttribute[] getAnnotations(){ + return annotations; + } + + @Override + public B accept(SysmlExprVisitor visitor, A a) throws IOException { + return visitor.visit(this, a); + } +} diff --git a/src/main/java/org/sysml/ast/SysmlPropertyType.java b/src/main/java/org/sysml/ast/SysmlPropertyType.java new file mode 100644 index 00000000..837ef6b0 --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlPropertyType.java @@ -0,0 +1,19 @@ +package org.sysml.ast; + +/** + * Sysml Property Keyword + * ::= ‘part’ | ‘reference’ | ‘value’ | + * + * TODO: accoutn for special cases + */ +public class SysmlPropertyType implements SysmlId { + private final String name; + public SysmlPropertyType(String name){ + this.name = name; + } + + @Override + public String getName() { + return name; + } +} diff --git a/src/main/java/org/sysml/ast/SysmlVisibilityOption.java b/src/main/java/org/sysml/ast/SysmlVisibilityOption.java new file mode 100644 index 00000000..524c3e68 --- /dev/null +++ b/src/main/java/org/sysml/ast/SysmlVisibilityOption.java @@ -0,0 +1,8 @@ +package org.sysml.ast; + +public enum SysmlVisibilityOption { + PLUS, // namespace-visibility + MINUS, // namespace-visibility + NUMBER, + TILDE +} diff --git a/src/main/java/org/sysml/compiler/AstSysmlCompiler.java b/src/main/java/org/sysml/compiler/AstSysmlCompiler.java new file mode 100644 index 00000000..3ecfdb39 --- /dev/null +++ b/src/main/java/org/sysml/compiler/AstSysmlCompiler.java @@ -0,0 +1,64 @@ +package org.sysml.compiler; + +import org.clafer.ast.AstAbstractClafer; +import org.clafer.ast.AstClafer; +import org.clafer.ast.AstModel; +import org.clafer.ast.AstRef; +import org.clafer.instance.InstanceClafer; +import org.sysml.ast.*; + +import java.util.*; + +public class AstSysmlCompiler { + private Map typeMap; + + public AstSysmlCompiler(){ + this.typeMap = new HashMap<>(); + this.typeMap.put("int", "Integer"); + this.typeMap.put("real", "Real"); + this.typeMap.put("double", "Real"); + this.typeMap.put("string", "String"); + } + + public SysmlPropertyDef[] compile(AstModel model, AstModel topLevelModel) { + ArrayList propDefs = new ArrayList(); + + for (AstAbstractClafer child: model.getAbstractRoot().getAbstractChildren()){ + String name = SysmlCompilerUtils.getPropertyId(child.getName()); + + ArrayList attrs = new ArrayList(); + for (AstClafer sub: child.getChildren()) { + Object sref = sub.getRef(); + if (sref instanceof AstRef){ + AstRef ref = (AstRef) sref; + Object tgt = typeMap.get(ref.getTargetType().toString()); + String propName = SysmlCompilerUtils.getPropertyId(ref.getSourceType().getName()); + attrs.add(new SysmlAttribute(propName, tgt)); + } + } + + String[] superClafers = SysmlCompilerUtils.getSuperClafers(child); + List hierarchy = Arrays.asList(superClafers); + if (hierarchy.size() < 3){ + continue; + } + + String[] superTypes = new String[0]; + if (hierarchy.contains("SysmlProperty")) { + superTypes = Arrays.copyOfRange(superClafers, 0, hierarchy.indexOf("SysmlProperty")-1); + } + + propDefs.add(new SysmlPropertyDef( + new SysmlBlockVisibility(SysmlVisibilityOption.PLUS), + new SysmlPropertyType("part"), + name, + new SysmlBlockDefElement[0], + attrs.toArray(new SysmlAttribute[attrs.size()]), + superTypes + )); + + } + + return propDefs.toArray(new SysmlPropertyDef[propDefs.size()]); + } +} diff --git a/src/main/java/org/sysml/compiler/InstanceSysmlCompiler.java b/src/main/java/org/sysml/compiler/InstanceSysmlCompiler.java new file mode 100644 index 00000000..4fdfdf61 --- /dev/null +++ b/src/main/java/org/sysml/compiler/InstanceSysmlCompiler.java @@ -0,0 +1,103 @@ +package org.sysml.compiler; + +import org.clafer.ast.AstClafer; +import org.clafer.ast.AstRef; +import org.clafer.instance.InstanceClafer; +import org.sysml.ast.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class InstanceSysmlCompiler { + + // it will use the FIRST clafer named SysmlProperty + public final String baseSysmlClafer = "SysmlProperty"; + + private ArrayList processedClafers; + + public InstanceSysmlCompiler(){ + this.processedClafers = new ArrayList(); + } + + int getMultiplicity(InstanceClafer model, AstClafer clafer){ + int count = 0; + if (model.getType().getName().equals(clafer.getName())){ + count++; + } + for (InstanceClafer child : model.getChildren()) { + count += getMultiplicity(child, clafer); + } + return count; + } + + public SysmlProperty compile(InstanceClafer model, InstanceClafer topLevelModel) { + // collect the identifier + String propertyName =SysmlCompilerUtils.getPropertyId(model.getType().getName()); + + // get its supers + String[] superClafers = SysmlCompilerUtils.getSuperClafers(model.getType()); + List hierarchy = Arrays.asList(superClafers); + + // process the children + ArrayList children = new ArrayList(); + for (InstanceClafer child : model.getChildren()) { + if (!processedClafers.contains(child.getType().getName())) { + Object _cchild = compile(child, topLevelModel); + if (_cchild != null) { + SysmlProperty cchild = (SysmlProperty) _cchild; + if (!cchild.getPropertyType().getName().equals("unk")) { + children.add(cchild); + } else { + SysmlBlockDefElement[] schildren = cchild.getElements(); + children.addAll(new ArrayList(Arrays.asList(schildren))); + } + } + } + } + + // get the property name + String propName = ""; + String[] superTypes = new String[0]; + if (hierarchy.contains(baseSysmlClafer)) { + propName = ((String) hierarchy.get(hierarchy.indexOf(baseSysmlClafer) - 1)).toLowerCase(); + superTypes = Arrays.copyOfRange(superClafers, 0, hierarchy.indexOf(baseSysmlClafer)-1); + } else { + propName = "unk"; + } + + // get the clafer multiplicity and mark as processed + int multiplicity = getMultiplicity(topLevelModel, model.getType()); + processedClafers.add(model.getType().getName()); + + // collect the annotations + ArrayList annots = new ArrayList(); + for (InstanceClafer child : model.getChildren()) { + Object ref = child.getType().getRef(); + if (ref != null) { + AstRef aref = (AstRef) ref; + String aname = SysmlCompilerUtils.getPropertyId(aref.getSourceType().getName()); + Object refv = child.getRef(); + annots.add(new SysmlAttribute(aname, refv)); + } + } + + // build a property object + if (propName.equals("unk") && children.size() == 0){ + return null; + } else { + return new SysmlProperty( + new SysmlBlockVisibility(SysmlVisibilityOption.PLUS), + new SysmlPropertyType(propName), + propertyName, + children.toArray(new SysmlBlockDefElement[children.size()]), + annots.toArray(new SysmlAttribute[annots.size()]), + superTypes, + multiplicity + ); + } + } + +} diff --git a/src/main/java/org/sysml/compiler/SysmlCompilerUtils.java b/src/main/java/org/sysml/compiler/SysmlCompilerUtils.java new file mode 100644 index 00000000..40afe812 --- /dev/null +++ b/src/main/java/org/sysml/compiler/SysmlCompilerUtils.java @@ -0,0 +1,34 @@ +package org.sysml.compiler; + +import org.clafer.ast.AstClafer; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SysmlCompilerUtils { + public static String getPropertyId(String name){ + Pattern pattern = Pattern.compile("(c[0-9]+_).*", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(name); + boolean matchFound = matcher.find(); + //matcher.group() + if (matchFound){ + return name.substring(matcher.group(1).length()); + } else { + return name; + } + } + + public static String[] getSuperClafers(AstClafer model){ + Object spr = model.getSuperClafer(); + if (spr == null){ + return new String[0]; + } else { + String[] rem = getSuperClafers((AstClafer) spr); + String[] sprs = Arrays.copyOf(rem, rem.length + 1); + sprs[0] = SysmlCompilerUtils.getPropertyId(((AstClafer) spr).getName()); + System.arraycopy(rem, 0, sprs, 1, rem.length); + return sprs; + } + } +} diff --git a/src/main/java/org/sysml/pprinter/SysmlPrinter.java b/src/main/java/org/sysml/pprinter/SysmlPrinter.java new file mode 100644 index 00000000..459419b7 --- /dev/null +++ b/src/main/java/org/sysml/pprinter/SysmlPrinter.java @@ -0,0 +1,102 @@ +package org.sysml.pprinter; + +import org.sysml.ast.*; + +import java.io.IOException; +import java.lang.Void; + +public class SysmlPrinter implements SysmlExprVisitor { + + private final String indent_base; + private final Appendable out; + + public SysmlPrinter(Appendable out) { + this.out = out; + this.indent_base = " "; + } + + + public void print(String indent, SysmlPackage spackage, Appendable out) throws IOException { + out.append(indent).append("package ").append(spackage.getName()).append(" {").append("\n"); + for (SysmlBlockDefElement elem : spackage.getElements()) { + print(indent + indent_base, elem, out); + out.append('\n'); + } + out.append(indent).append("}"); + } + + public void print(String indent, SysmlBlockDefElement sprop, Appendable out) throws IOException { + } + + public Void visit(SysmlAttribute ast, String indent) throws IOException { + this.out.append(indent).append(":>> ").append(ast.getName()).append(" = ").append(ast.getRef()).append(";\n"); + return null; + } + + @Override + public Void visit(SysmlProperty ast, String indent) throws IOException { + this.out + .append(indent) + .append(ast.getPropertyType().getName()) + .append(" ") + .append(ast.getName()) + ; + if (ast.getMultiplicity() > 1) { + this.out.append("[").append(String.valueOf(ast.getMultiplicity())).append("]"); + } + for (String s: ast.getSupers()){ + this.out.append(" : ").append(s); + } + if (ast.getElements().length > 0 || ast.getAnnotations().length > 0) { + this.out.append(" {\n"); + for (SysmlAttribute annot: ast.getAnnotations()){ + annot.accept(this, indent + indent_base); + } + for (SysmlBlockDefElement elem : ast.getElements()) { + elem.accept(this, indent + indent_base); + } + this.out.append(indent).append("}\n"); + } else { + this.out.append(";\n"); + } + + return null; + } + + @Override + public Void visit(SysmlPropertyDef ast, String indent) throws IOException { + this.out + .append(indent) + .append(ast.getPropertyType().getName()) + .append(" def ") + .append(ast.getName()) + ; + for (String s: ast.getSupers()){ + this.out.append(" :> ").append(s); + } + if (ast.getElements().length > 0 || ast.getAnnotations().length > 0) { + this.out.append(" {\n"); + for (SysmlAttribute annot: ast.getAnnotations()){ + this.out.append(indent + indent_base).append("attribute ").append(annot.getName()).append(": ").append(annot.getRef()).append(";\n"); + ///annot.accept(this, indent + indent_base); + } + for (SysmlBlockDefElement elem : ast.getElements()) { + elem.accept(this, indent + indent_base); + } + this.out.append(indent).append("}\n"); + } else { + this.out.append(";\n"); + } + return null; + } + + @Override + public Void visit(SysmlPackage ast, String indent) throws IOException { + this.out.append(indent).append("package ").append(ast.getName()).append(" {").append("\n"); + for (SysmlBlockDefElement elem : ast.getElements()) { + elem.accept(this, indent + indent_base); + } + this.out.append(indent).append("}\n"); + return null; + } +} diff --git a/src/test/java/org/sysml/SysmlPropertyTest.java b/src/test/java/org/sysml/SysmlPropertyTest.java new file mode 100644 index 00000000..6f138b8e --- /dev/null +++ b/src/test/java/org/sysml/SysmlPropertyTest.java @@ -0,0 +1,117 @@ +package org.sysml; + +import org.clafer.ast.AstClafer; +import org.clafer.ast.AstModel; +import org.clafer.cli.Utils; +import org.clafer.compiler.ClaferCompiler; +import org.clafer.compiler.ClaferOption; +import org.clafer.compiler.ClaferSearch; +import org.clafer.instance.InstanceClafer; +import org.clafer.instance.InstanceModel; +import org.clafer.javascript.Javascript; +import org.clafer.javascript.JavascriptFile; +import org.clafer.objective.Objective; +import org.clafer.scope.Scope; +import org.junit.Test; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.sysml.ast.SysmlPropertyDef; +import org.sysml.compiler.AstSysmlCompiler; +import org.sysml.compiler.InstanceSysmlCompiler; +import org.sysml.compiler.SysmlCompilerUtils; +import test.OptimizationTest; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertTrue; + +@Ignore +@RunWith(Parameterized.class) +public class SysmlPropertyTest { + @Parameterized.Parameter + public File testFile; + + @Parameterized.Parameters(name = "{0}") + public static List testFiles() throws URISyntaxException { + File dir = new File(OptimizationTest.class.getResource("/sysml-samples/assert-positive").toURI()); + assertTrue(dir.isDirectory()); + List files = new ArrayList<>(); + for (File file : dir.listFiles()) { + if (file.getAbsolutePath().endsWith(".cfr")) { + files.add(new File[]{file}); + } + } + return files; + } + + File getInputFile() throws IOException, InterruptedException { + Process compilerProcess = Runtime.getRuntime().exec("clafer -k -m choco " + testFile); + compilerProcess.waitFor(); + + // replace the extension to .js + String testFileName = testFile.getAbsolutePath(); + int extPos = testFileName.lastIndexOf("."); + if(extPos != -1) { + testFileName = testFileName.substring(0, extPos) + ".js"; + } + + // change the inputFile to the resulting .js file + return new File(testFileName); + } + + SysmlPropertyDef[] getSysmlPropertyDefs(File inputFile) throws IOException { + // compile the example + JavascriptFile jsFile = Javascript.readModel(inputFile); + AstModel top = jsFile.getModel(); + AstSysmlCompiler compiler = new AstSysmlCompiler(); + SysmlPropertyDef[] models = compiler.compile(top, top); + return models; + } + + + /* + * Test that the compiler creates SysML Properties + */ + @Test + public void testSysmlProperty() throws IOException, URISyntaxException, InterruptedException { + File inputFile = getInputFile(); + JavascriptFile jsFile = Javascript.readModel(inputFile); + + Objective[] objectives = jsFile.getObjectives(); + + // handle scopes + /* setting the default int range */ + Scope scope = jsFile.getScope(); + int scopeHighDef = 127; + int scopeLowDef = -(scopeHighDef + 1); + scope = scope.toBuilder().intLow(scopeLowDef).intHigh(scopeHighDef).toScope(); + + // handle search strategy + ClaferOption compilerOption = jsFile.getOption(); + + // pick the right solver + ClaferSearch solver = objectives.length == 0 + ? ClaferCompiler.compile(jsFile.getModel(), scope, compilerOption) + : ClaferCompiler.compile(jsFile.getModel(), scope, objectives, compilerOption); + assertTrue(solver.find()); + InstanceModel instance = solver.instance(); + + // the instance contains something + assertTrue(instance.getTopClafers().length > 0); + + // get its supers + for (InstanceClafer clafer: instance.getTopClafers()) { + String[] superClafers = SysmlCompilerUtils.getSuperClafers(clafer.getType()); + List hierarchy = Arrays.asList(superClafers); + assertTrue(hierarchy.contains(new InstanceSysmlCompiler().baseSysmlClafer)); + } + + System.out.println(testFile); + } +} diff --git a/src/test/resources/sysml-samples/assert-positive/sysmlproperty0.cfr b/src/test/resources/sysml-samples/assert-positive/sysmlproperty0.cfr new file mode 100644 index 00000000..1d0b1f00 --- /dev/null +++ b/src/test/resources/sysml-samples/assert-positive/sysmlproperty0.cfr @@ -0,0 +1,5 @@ +abstract SysmlProperty + +abstract Part: SysmlProperty + +myClafer: Part diff --git a/src/test/resources/sysml-samples/assert-positive/sysmlproperty1.cfr b/src/test/resources/sysml-samples/assert-positive/sysmlproperty1.cfr new file mode 100644 index 00000000..910cd904 --- /dev/null +++ b/src/test/resources/sysml-samples/assert-positive/sysmlproperty1.cfr @@ -0,0 +1,5 @@ +abstract SysmlProperty + +abstract Action: SysmlProperty + +myClafer: Action diff --git a/src/test/resources/sysml-samples/assert-positive/sysmlproperty2.cfr b/src/test/resources/sysml-samples/assert-positive/sysmlproperty2.cfr new file mode 100644 index 00000000..1305f441 --- /dev/null +++ b/src/test/resources/sysml-samples/assert-positive/sysmlproperty2.cfr @@ -0,0 +1,8 @@ +abstract SysmlProperty + +abstract Part: SysmlProperty + +abstract Action: SysmlProperty + +myClafer: Part + myAction: Action