diff --git a/.gitignore b/.gitignore index d0cc242a..007e314b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ generated/ /.metadata/ /build/ cnf/release/ -cnf/local/ src-gen \ No newline at end of file diff --git a/cnf/central.mvn b/cnf/central.mvn index 1c3b2b92..90dd22ba 100644 --- a/cnf/central.mvn +++ b/cnf/central.mvn @@ -150,7 +150,6 @@ org.geckoprojects.bnd:org.gecko.bnd.dimc.library:1.1.1 org.geckoprojects.bnd:org.gecko.bnd.osgitest.library:1.1.1 org.geckoprojects.bnd:org.gecko.bnd.jacoco.library:1.1.1 -#org.geckoprojects.emf:org.gecko.emf.osgi.api:4.1.1.202202162308 org.geckoprojects.emf:org.gecko.emf.osgi.bnd.library.workspace:4.1.1-SNAPSHOT com.fasterxml.jackson.core:jackson-core:2.14.1 @@ -163,3 +162,9 @@ org.eclipse.emfcloud:emfjson-jackson:2.2.0 de.undercouch:bson4jackson:2.13.1 org.yaml:snakeyaml:1.33 org.geckoprojects.emf:org.gecko.emf.osgi.example.model.basic:4.1.1-SNAPSHOT + +org.apache.commons:commons-text:1.10.0 +org.apache.commons:commons-lang3:3.12.0 + +com.google.guava:guava:31.1-jre +com.google.guava:failureaccess:1.0.1 diff --git a/cnf/local/com.github.miachm.sods/com.github.miachm.sods-1.5.3.jar b/cnf/local/com.github.miachm.sods/com.github.miachm.sods-1.5.3.jar new file mode 100644 index 00000000..f234d2d8 Binary files /dev/null and b/cnf/local/com.github.miachm.sods/com.github.miachm.sods-1.5.3.jar differ diff --git a/cnf/local/index.xml b/cnf/local/index.xml new file mode 100644 index 00000000..74466bc8 --- /dev/null +++ b/cnf/local/index.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.gecko.emf.exporter.ods.tests/.classpath b/org.gecko.emf.exporter.ods.tests/.classpath new file mode 100644 index 00000000..66e477cd --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.gecko.emf.exporter.ods.tests/.gitignore b/org.gecko.emf.exporter.ods.tests/.gitignore new file mode 100644 index 00000000..7fdbdef7 --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/bin_test/ diff --git a/org.gecko.emf.exporter.ods.tests/.project b/org.gecko.emf.exporter.ods.tests/.project new file mode 100644 index 00000000..711a40ed --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/.project @@ -0,0 +1,23 @@ + + + org.gecko.emf.exporter.ods.tests + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/org.gecko.emf.exporter.ods.tests/.settings/org.eclipse.core.resources.prefs b/org.gecko.emf.exporter.ods.tests/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..b905f5e1 --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//.settings/org.eclipse.core.resources.prefs=UTF-8 +encoding//.settings/org.eclipse.jdt.core.prefs=UTF-8 +encoding//.settings/org.eclipse.jdt.ui.prefs=UTF-8 +encoding/bnd.bnd=UTF-8 +encoding/test.bndrun=UTF-8 diff --git a/org.gecko.emf.exporter.ods.tests/.settings/org.eclipse.jdt.core.prefs b/org.gecko.emf.exporter.ods.tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..f2525a8b --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/org.gecko.emf.exporter.ods.tests/bnd.bnd b/org.gecko.emf.exporter.ods.tests/bnd.bnd new file mode 100644 index 00000000..12c04331 --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/bnd.bnd @@ -0,0 +1,17 @@ +-enable-junit5: true +-library: enable-emf + +javac.source: 11 +javac.target: 11 + +Bundle-Version: 1.0.0.SNAPSHOT + +-buildpath: \ + org.gecko.emf.osgi.component,\ + org.eclipse.emf.ecore.xmi,\ + org.eclipse.emf.ecore,\ + org.gecko.emf.osgi.example.model.basic,\ + org.gecko.emf.exporter;version=latest,\ + org.apache.commons.commons-text,\ + org.gecko.emf.util.model,\ + com.github.miachm.sods diff --git a/org.gecko.emf.exporter.ods.tests/src/org/gecko/emf/ods/tests/EMFODSExporterTest.java b/org.gecko.emf.exporter.ods.tests/src/org/gecko/emf/ods/tests/EMFODSExporterTest.java new file mode 100644 index 00000000..e443ebd3 --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/src/org/gecko/emf/ods/tests/EMFODSExporterTest.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2012 - 2023 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.gecko.emf.ods.tests.helper.EMFODSExporterTestHelper.createBasicPackageResourceSet; +import static org.gecko.emf.ods.tests.helper.EMFODSExporterTestHelper.createBusinessPerson; +import static org.gecko.emf.ods.tests.helper.EMFODSExporterTestHelper.createFlintstonesFamily; +import static org.gecko.emf.ods.tests.helper.EMFODSExporterTestHelper.createRequest; +import static org.gecko.emf.ods.tests.helper.EMFODSExporterTestHelper.createSimpsonFamily; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.gecko.emf.exporter.EMFExportOptions; +import org.gecko.emf.exporter.EMFExporter; +import org.gecko.emf.osgi.example.model.basic.BasicFactory; +import org.gecko.emf.osgi.example.model.basic.BasicPackage; +import org.gecko.emf.osgi.example.model.basic.BusinessPerson; +import org.gecko.emf.osgi.example.model.basic.Family; +import org.gecko.emf.utilities.Request; +import org.gecko.emf.utilities.UtilitiesFactory; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.commons.annotation.Testable; +import org.osgi.framework.ServiceReference; +import org.osgi.test.common.annotation.InjectService; +import org.osgi.test.common.service.ServiceAware; +import org.osgi.test.junit5.context.BundleContextExtension; +import org.osgi.test.junit5.service.ServiceExtension; + +/** + * EMF ODS exporter integration test. + * + * @author Michal H. Siemaszko + */ +@Testable +@ExtendWith(BundleContextExtension.class) +@ExtendWith(ServiceExtension.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class EMFODSExporterTest { + + @Order(value = -1) + @Test + public void testServices( + @InjectService(cardinality = 1, timeout = 4000, filter = "(component.name=EMFODSExporter)") ServiceAware emfOdsExporterAware) { + + assertThat(emfOdsExporterAware.getServices()).hasSize(1); + ServiceReference emfOdsExporterReference = emfOdsExporterAware.getServiceReference(); + assertThat(emfOdsExporterReference).isNotNull(); + } + + @Test + public void testExportBasicPackageResourceToOds( + @InjectService(cardinality = 1, timeout = 4000, filter = "(component.name=EMFODSExporter)") ServiceAware emfOdsExporterAware, + @InjectService BasicFactory basicFactory, @InjectService BasicPackage basicPackage) throws Exception { + + assertThat(emfOdsExporterAware.getServices()).hasSize(1); + EMFExporter emfOdsExporterService = emfOdsExporterAware.getService(); + assertThat(emfOdsExporterService).isNotNull(); + + ResourceSet resourceSet = createBasicPackageResourceSet(basicPackage); + Resource xmiResource = resourceSet.createResource(URI.createURI("basicPackageExporter.test")); + assertNotNull(xmiResource); + + Family simpsonFamily = createSimpsonFamily(basicFactory); + xmiResource.getContents().add(simpsonFamily); + + Family flintstonesFamily = createFlintstonesFamily(basicFactory); + xmiResource.getContents().add(flintstonesFamily); + + BusinessPerson businessPerson = createBusinessPerson(basicFactory); + xmiResource.getContents().add(businessPerson); + + Path filePath = Files.createTempFile("testBasicPackageExport", ".ods"); + + OutputStream fileOutputStream = Files.newOutputStream(filePath); + + // @formatter:off + emfOdsExporterService.exportResourceTo(xmiResource, fileOutputStream, + Map.of( + EMFExportOptions.OPTION_LOCALE, Locale.GERMANY, + EMFExportOptions.OPTION_EXPORT_NONCONTAINMENT, true, + EMFExportOptions.OPTION_EXPORT_METADATA, true, + EMFExportOptions.OPTION_ADJUST_COLUMN_WIDTH, true, + EMFExportOptions.OPTION_GENERATE_LINKS, true, + EMFExportOptions.OPTION_ADD_MAPPING_TABLE, true + ) + ); + // @formatter:on + } + + @Test + public void testExportUtilitiesPackageResourceToOds( + @InjectService(cardinality = 1, timeout = 4000, filter = "(component.name=EMFODSExporter)") ServiceAware emfOdsExporterAware) + throws Exception { + + assertThat(emfOdsExporterAware.getServices()).hasSize(1); + EMFExporter emfOdsExporterService = emfOdsExporterAware.getService(); + assertThat(emfOdsExporterService).isNotNull(); + + Request request = createRequest(UtilitiesFactory.eINSTANCE); + + Path filePath = Files.createTempFile("testUtilitiesPackageExport", ".ods"); + + OutputStream fileOutputStream = Files.newOutputStream(filePath); + + // @formatter:off + emfOdsExporterService.exportEObjectsTo(List.of(request), fileOutputStream, + Map.of( + EMFExportOptions.OPTION_LOCALE, Locale.GERMANY, + EMFExportOptions.OPTION_EXPORT_NONCONTAINMENT, true, + EMFExportOptions.OPTION_EXPORT_METADATA, true, + EMFExportOptions.OPTION_ADJUST_COLUMN_WIDTH, true, + EMFExportOptions.OPTION_GENERATE_LINKS, true, + EMFExportOptions.OPTION_ADD_MAPPING_TABLE, true + ) + ); + // @formatter:on + } +} diff --git a/org.gecko.emf.exporter.ods.tests/src/org/gecko/emf/ods/tests/helper/EMFODSExporterTestHelper.java b/org.gecko.emf.exporter.ods.tests/src/org/gecko/emf/ods/tests/helper/EMFODSExporterTestHelper.java new file mode 100644 index 00000000..a2b7194f --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/src/org/gecko/emf/ods/tests/helper/EMFODSExporterTestHelper.java @@ -0,0 +1,412 @@ +/** + * Copyright (c) 2012 - 2023 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.tests.helper; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +import org.apache.commons.text.RandomStringGenerator; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.gecko.emf.osgi.example.model.basic.Address; +import org.gecko.emf.osgi.example.model.basic.BasicFactory; +import org.gecko.emf.osgi.example.model.basic.BasicPackage; +import org.gecko.emf.osgi.example.model.basic.BusinessPerson; +import org.gecko.emf.osgi.example.model.basic.ContactContextType; +import org.gecko.emf.osgi.example.model.basic.ContactType; +import org.gecko.emf.osgi.example.model.basic.EmployeeInfo; +import org.gecko.emf.osgi.example.model.basic.Family; +import org.gecko.emf.osgi.example.model.basic.GenderType; +import org.gecko.emf.osgi.example.model.basic.Person; +import org.gecko.emf.osgi.example.model.basic.PersonContact; +import org.gecko.emf.osgi.example.model.basic.Tag; +import org.gecko.emf.osgi.example.model.basic.util.BasicResourceFactoryImpl; +import org.gecko.emf.utilities.Filter; +import org.gecko.emf.utilities.Request; +import org.gecko.emf.utilities.Sort; +import org.gecko.emf.utilities.SortType; +import org.gecko.emf.utilities.UtilitiesFactory; + +public class EMFODSExporterTestHelper { + + public static Request createRequest(UtilitiesFactory uf) { + Request request = uf.createRequest(); + + Instant now = Instant.now(); + + request.setId(UUID.randomUUID().toString()); + request.setFrom(Date.from(now)); + request.setTo(Date.from(now.plus(7, ChronoUnit.DAYS))); + + Sort sort = uf.createSort(); + sort.setIndex(0); + sort.setField("cartoon"); + sort.setType(SortType.ASCENDING); + + request.getSorting().add(sort); + + Filter filter = uf.createFilter(); + filter.setIndex(0); + filter.setField("cartoon"); + filter.getValue().add("Simpsons"); + + request.getFiltering().add(filter); + + return request; + } + + public static Family createSimpsonFamily(BasicFactory bf) { + Family simpsonFamily = bf.createFamily(); + simpsonFamily.setId("Simpsons"); + + Address address = createSimpsonsAddress(bf); + + Person homerSimpson = createHomerSimpson(bf, address); + simpsonFamily.setFather(homerSimpson); + + Person margeSimpson = createMargeSimpson(bf, address); + simpsonFamily.setMother(margeSimpson); + + Person bartSimpson = createBartSimpson(bf, address); + simpsonFamily.getChildren().add(bartSimpson); + + Person lisaSimpson = createLisaSimpson(bf, address); + simpsonFamily.getChildren().add(lisaSimpson); + + Person maggieSimpson = createMaggieSimpson(bf, address); + simpsonFamily.getChildren().add(maggieSimpson); + + homerSimpson.getRelatives().add(margeSimpson); + homerSimpson.getRelatives().add(bartSimpson); + homerSimpson.getRelatives().add(lisaSimpson); + homerSimpson.getRelatives().add(maggieSimpson); + + margeSimpson.getRelatives().add(homerSimpson); + margeSimpson.getRelatives().add(bartSimpson); + margeSimpson.getRelatives().add(lisaSimpson); + margeSimpson.getRelatives().add(maggieSimpson); + + bartSimpson.getRelatives().add(homerSimpson); + bartSimpson.getRelatives().add(margeSimpson); + bartSimpson.getRelatives().add(lisaSimpson); + bartSimpson.getRelatives().add(maggieSimpson); + + lisaSimpson.getRelatives().add(homerSimpson); + lisaSimpson.getRelatives().add(margeSimpson); + lisaSimpson.getRelatives().add(bartSimpson); + lisaSimpson.getRelatives().add(maggieSimpson); + + maggieSimpson.getRelatives().add(homerSimpson); + maggieSimpson.getRelatives().add(margeSimpson); + maggieSimpson.getRelatives().add(lisaSimpson); + maggieSimpson.getRelatives().add(maggieSimpson); + + homerSimpson.getTags().add(createMultiLevelTag(bf, createUniquePrefix(10))); + + homerSimpson.setBigInt(BigInteger.TEN); + + homerSimpson.getBigDec().add(BigDecimal.ZERO); + homerSimpson.getBigDec().add(BigDecimal.ONE); + homerSimpson.getBigDec().add(BigDecimal.TEN); + + homerSimpson.setImage(createByteArr()); + + homerSimpson.getProperties().putAll(createProperties(createUniquePrefix(10))); + + return simpsonFamily; + } + + private static Address createSimpsonsAddress(BasicFactory bf) { + return createAddress(bf, "742 Evergreen Terrace", "Springfield", "97482"); + } + + private static Person createHomerSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Homer", "Simpson", GenderType.MALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createMargeSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Marge", "Simpson", GenderType.FEMALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createBartSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Bart", "Simpson", GenderType.MALE, address); + + return p; + } + + private static Person createLisaSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Lisa", "Simpson", GenderType.FEMALE, address); + + return p; + } + + private static Person createMaggieSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Maggie", "Simpson", GenderType.FEMALE, address); + + return p; + } + + public static Family createFlintstonesFamily(BasicFactory bf) { + Family flintstonesFamily = bf.createFamily(); + flintstonesFamily.setId("Flintstones"); + + Address address = createFlintstonesAddress(bf); + + Person fredFlintstone = createFredFlintstone(bf, address); + flintstonesFamily.setFather(fredFlintstone); + + Person wilmaFlintstone = createWilmaFlintstone(bf, address); + flintstonesFamily.setMother(wilmaFlintstone); + + Person pebblesFlintstone = createPebblesFlintstone(bf, address); + flintstonesFamily.getChildren().add(pebblesFlintstone); + + Person stonyFlintstone = createStonyFlintstone(bf, address); + flintstonesFamily.getChildren().add(stonyFlintstone); + + fredFlintstone.getRelatives().add(wilmaFlintstone); + fredFlintstone.getRelatives().add(pebblesFlintstone); + fredFlintstone.getRelatives().add(stonyFlintstone); + + wilmaFlintstone.getRelatives().add(fredFlintstone); + wilmaFlintstone.getRelatives().add(pebblesFlintstone); + wilmaFlintstone.getRelatives().add(stonyFlintstone); + + pebblesFlintstone.getRelatives().add(fredFlintstone); + pebblesFlintstone.getRelatives().add(wilmaFlintstone); + pebblesFlintstone.getRelatives().add(stonyFlintstone); + + stonyFlintstone.getRelatives().add(fredFlintstone); + stonyFlintstone.getRelatives().add(wilmaFlintstone); + stonyFlintstone.getRelatives().add(pebblesFlintstone); + + fredFlintstone.getTags().add(createMultiLevelTag(bf, createUniquePrefix(10))); + + fredFlintstone.setBigInt(BigInteger.TEN); + + fredFlintstone.getBigDec().add(BigDecimal.ZERO); + fredFlintstone.getBigDec().add(BigDecimal.ONE); + fredFlintstone.getBigDec().add(BigDecimal.TEN); + + fredFlintstone.setImage(createByteArr()); + + fredFlintstone.getProperties().putAll(createProperties(createUniquePrefix(10))); + + return flintstonesFamily; + } + + private static Address createFlintstonesAddress(BasicFactory bf) { + return createAddress(bf, "301 Cobblestone Way", "Bedrock", "70777"); + } + + private static Person createFredFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Fred", "Flintstone", GenderType.MALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createWilmaFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Wilma", "Flintstone", GenderType.FEMALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createPebblesFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Pebbles", "Flintstone", GenderType.FEMALE, address); + + return p; + } + + private static Person createStonyFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Stony", "Flintstone", GenderType.MALE, address); + + return p; + } + + public static BusinessPerson createBusinessPerson(BasicFactory bf) { + BusinessPerson bp = bf.createBusinessPerson(); + + bp.setId(UUID.randomUUID().toString()); + bp.setFirstName("Thomas"); + bp.setLastName("Edison"); + bp.setGender(GenderType.MALE); + + bp.setCompanyIdCardNumber(UUID.randomUUID().toString()); + + EmployeeInfo nikolaTesla = bf.createEmployeeInfo(); + nikolaTesla.setPosition("one-time employee"); + bp.getEmployeeInfo().add(nikolaTesla); + + return bp; + } + + private static Person createPerson(BasicFactory bf, String firstName, String lastName, GenderType gender, + Address address) { + Person p = bf.createPerson(); + + p.setId(UUID.randomUUID().toString()); + p.setFirstName(firstName); + p.setLastName(lastName); + p.setGender(gender); + + p.setAddress(address); + + return p; + } + + private static Address createAddress(BasicFactory bf, String street, String city, String zip) { + Address a = bf.createAddress(); + + a.setId(UUID.randomUUID().toString()); + a.setStreet(street); + a.setCity(city); + a.setZip(zip); + + return a; + } + + private static PersonContact createHomePhonePersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.PHONE, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeMobilePersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.MOBILE, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeWhatsAppPersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.WHATSAPP, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeEmailPersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.EMAIL, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeSkypePersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.SKYPE, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeWebAddressPersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.WEBADDRESS, ContactContextType.HOME, UUID.randomUUID().toString(), + p); + } + + private static PersonContact createPersonContact(BasicFactory bf, ContactType type, ContactContextType context, + String value, Person p) { + PersonContact pc = bf.createPersonContact(); + + pc.setContext(context); + pc.setType(type); + pc.setValue(value); + + pc.setContactPerson(p); + + return pc; + } + + private static Tag createMultiLevelTag(BasicFactory bf, String namePrefix) { + Tag t1 = createTag(bf, namePrefix, "tag_level_1", "tag_level_1_value", "tag_level_1_description"); + + t1.setTag(createTag(bf, namePrefix, "tag_level_2", "tag_level_2_value", "tag_level_2_description")); + + t1.getTags().add(createTag(bf, namePrefix, "tag_level_3", "tag_level_3_value", "tag_level_3_description")); + + return t1; + } + + private static Tag createTag(BasicFactory bf, String namePrefix, String name, String value, String description) { + Tag t = bf.createTag(); + + t.setName(namePrefix + "_" + name); + t.setValue(value); + t.setDescription(description); + + return t; + } + + private static byte[] createByteArr() { + byte[] b = new byte[20]; + new Random().nextBytes(b); + return b; + } + + private static Map createProperties(String namePrefix) { + Map props = new HashMap(); + + props.put(createPropertyName(namePrefix, "prop_1"), "prop_1_value"); + props.put(createPropertyName(namePrefix, "prop_2"), "prop_2_value"); + props.put(createPropertyName(namePrefix, "prop_3"), "prop_3_value"); + props.put(createPropertyName(namePrefix, "prop_4"), "prop_4_value"); + + return props; + } + + private static String createPropertyName(String prefix, String name) { + return (prefix + "_" + name); + } + + public static ResourceSet createBasicPackageResourceSet(BasicPackage bp) { + ResourceSet resourceSet = new ResourceSetImpl(); + resourceSet.getPackageRegistry().put(BasicPackage.eNS_URI, bp); + resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("test", new BasicResourceFactoryImpl()); + resourceSet.getResourceFactoryRegistry().getContentTypeToFactoryMap().put(BasicPackage.eCONTENT_TYPE, + new BasicResourceFactoryImpl()); + return resourceSet; + } + + private static String createUniquePrefix(int maxChars) { + // @formatter:off + return new RandomStringGenerator.Builder() + .withinRange('a', 'z') + .build() + .generate(maxChars); + // @formatter:on + } +} diff --git a/org.gecko.emf.exporter.ods.tests/test.bndrun b/org.gecko.emf.exporter.ods.tests/test.bndrun new file mode 100644 index 00000000..29ef1b41 --- /dev/null +++ b/org.gecko.emf.exporter.ods.tests/test.bndrun @@ -0,0 +1,58 @@ + +-runfw: org.apache.felix.framework;version='[7.0.1,7.0.1]' +-runprovidedcapabilities: ${native_capability} + +-resolve.effective: active + +-runbundles.junit5: ${test.runbundles} + +-runbundles: \ + junit-jupiter-api;version='[5.8.2,5.8.3)',\ + junit-platform-commons;version='[1.8.2,1.8.3)',\ + org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ + org.apache.felix.scr;version='[2.1.30,2.1.31)',\ + org.eclipse.emf.common;version='[2.23.0,2.23.1)',\ + org.eclipse.emf.ecore;version='[2.25.0,2.25.1)',\ + org.eclipse.emf.ecore.xmi;version='[2.16.0,2.16.1)',\ + org.gecko.emf.osgi.api;version='[4.1.1,4.1.2)',\ + org.gecko.emf.osgi.component;version='[4.1.1,4.1.2)',\ + org.gecko.emf.osgi.example.model.basic;version='[4.1.1,4.1.2)',\ + org.opentest4j;version='[1.2.0,1.2.1)',\ + org.osgi.util.function;version='[1.1.0,1.1.1)',\ + org.osgi.util.pushstream;version='[1.0.1,1.0.2)',\ + org.apache.felix.converter;version='[1.0.18,1.0.19)',\ + org.osgi.util.promise;version='[1.2.0,1.2.1)',\ + assertj-core;version='[3.22.0,3.22.1)',\ + junit-jupiter-params;version='[5.8.2,5.8.3)',\ + org.osgi.test.common;version='[1.1.0,1.1.1)',\ + org.osgi.test.junit5;version='[1.1.0,1.1.1)',\ + org.yaml.snakeyaml;version='[1.33.0,1.33.1)',\ + org.gecko.emf.exporter;version=snapshot,\ + org.gecko.emf.exporter.ods;version=snapshot,\ + org.gecko.emf.exporter.ods.tests;version=snapshot,\ + com.fasterxml.jackson.core.jackson-annotations;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.core.jackson-core;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.core.jackson-databind;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.dataformat.jackson-dataformat-properties;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.14.1,2.14.2)',\ + de.undercouch.bson4jackson;version='[2.13.1,2.13.2)',\ + org.apache.commons.commons-text;version='[1.10.0,1.10.1)',\ + org.apache.commons.lang3;version='[3.12.0,3.12.1)',\ + org.eclipse.emfcloud.emfjson-jackson;version='[2.2.0,2.2.1)',\ + org.gecko.emf.json;version=snapshot,\ + org.gecko.emf.util.model;version=snapshot,\ + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + com.github.miachm.sods;version='[1.5.3,1.5.4)',\ + org.gecko.emf.pushstreams;version=snapshot,\ + com.fasterxml.jackson.datatype.jackson-datatype-jsr310;version='[2.14.1,2.14.2)' + +-runrequires: bnd.identity;id='org.gecko.emf.exporter.ods.tests' + +-runee: JavaSE-11 + +-runtrace: true + +-runproperties.debug: \ + felix.log.level=4,\ + org.osgi.service.log.admin.loglevel=DEBUG + diff --git a/org.gecko.emf.exporter.ods/.classpath b/org.gecko.emf.exporter.ods/.classpath new file mode 100644 index 00000000..66e477cd --- /dev/null +++ b/org.gecko.emf.exporter.ods/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.gecko.emf.exporter.ods/.gitignore b/org.gecko.emf.exporter.ods/.gitignore new file mode 100644 index 00000000..7fdbdef7 --- /dev/null +++ b/org.gecko.emf.exporter.ods/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/bin_test/ diff --git a/org.gecko.emf.exporter.ods/.project b/org.gecko.emf.exporter.ods/.project new file mode 100644 index 00000000..739cd2ab --- /dev/null +++ b/org.gecko.emf.exporter.ods/.project @@ -0,0 +1,23 @@ + + + org.gecko.emf.exporter.ods + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/org.gecko.emf.exporter.ods/.settings/org.eclipse.core.resources.prefs b/org.gecko.emf.exporter.ods/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..e8cd65cb --- /dev/null +++ b/org.gecko.emf.exporter.ods/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//.settings/org.eclipse.core.resources.prefs=UTF-8 +encoding//.settings/org.eclipse.jdt.core.prefs=UTF-8 +encoding//.settings/org.eclipse.jdt.ui.prefs=UTF-8 +encoding/bnd.bnd=UTF-8 diff --git a/org.gecko.emf.exporter.ods/.settings/org.eclipse.jdt.core.prefs b/org.gecko.emf.exporter.ods/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..f2525a8b --- /dev/null +++ b/org.gecko.emf.exporter.ods/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/org.gecko.emf.exporter.ods/bnd.bnd b/org.gecko.emf.exporter.ods/bnd.bnd new file mode 100644 index 00000000..9fc5e408 --- /dev/null +++ b/org.gecko.emf.exporter.ods/bnd.bnd @@ -0,0 +1,14 @@ +Bundle-Version: 1.0.0.SNAPSHOT +Bundle-Name: Gecko EMF ODS Exporter +Bundle-Description: ODS (OpenDocument Spreadsheet) Exporter for EMF + +-library: enable-emf + +-buildpath: \ + org.gecko.emf.exporter,\ + org.apache.commons.commons-text;version='1.10',\ + com.google.guava;version='31.1',\ + com.github.miachm.sods + +Private-Package: \ + org.gecko.emf.exporter.ods diff --git a/org.gecko.emf.exporter.ods/src/org/gecko/emf/exporter/ods/EMFODSExporter.java b/org.gecko.emf.exporter.ods/src/org/gecko/emf/exporter/ods/EMFODSExporter.java new file mode 100644 index 00000000..aa14b2b4 --- /dev/null +++ b/org.gecko.emf.exporter.ods/src/org/gecko/emf/exporter/ods/EMFODSExporter.java @@ -0,0 +1,1303 @@ +/** + * Copyright (c) 2012 - 2023 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.exporter.ods; + +import static java.util.stream.Collectors.toList; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import org.apache.commons.text.WordUtils; +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EEnum; +import org.eclipse.emf.ecore.EEnumLiteral; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.gecko.emf.exporter.EMFExportException; +import org.gecko.emf.exporter.EMFExportOptions; +import org.gecko.emf.exporter.EMFExporter; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ServiceScope; + +import com.github.miachm.sods.Color; +import com.github.miachm.sods.LinkedValue; +import com.github.miachm.sods.Range; +import com.github.miachm.sods.Sheet; +import com.github.miachm.sods.SpreadSheet; +import com.github.miachm.sods.Style; + +/** + * Implementation of the {@link EMFExporter} to provide support for exporting EMF resources and lists of EMF objects to ODS format. + * + * @author Michal H. Siemaszko + */ +@Component(name = "EMFODSExporter", scope = ServiceScope.PROTOTYPE) +public class EMFODSExporter implements EMFExporter { + + private static final int MAX_CHAR_PER_LINE_DEFAULT = 30; + + private static final Style HEADER_STYLE = new Style(); + static { + HEADER_STYLE.setBackgroundColor(new Color("#a3a3a3")); + HEADER_STYLE.setFontColor(new Color("#000000")); + HEADER_STYLE.setBold(true); + HEADER_STYLE.setTextAligment(Style.TEXT_ALIGMENT.Center); + } + + private static final Style WRAPPED_DATA_CELL_STYLE = new Style(); + static { + WRAPPED_DATA_CELL_STYLE.setWrap(true); + } + + private static final List METADATA_ECLASS_SHEET_HEADERS = List.of("Name", "Type", "isMany", "isRequired", + "Default value", "Documentation"); + private static final List METADATA_EENUM_SHEET_HEADERS = List.of("Name", "Literal", "Value", + "Documentation"); + private static final String METADATA_DOCUMENTATION_HEADER = "Documentation"; + + private static final String METADATA_SHEET_SUFFIX = "Metadata"; + private static final String MAPPING_TABLE_SHEET_SUFFIX = "Mapping Table"; + + private static final String DOCUMENTATION_GENMODEL_SOURCE = "http://www.eclipse.org/emf/2002/GenModel"; + private static final String DOCUMENTATION_GENMODEL_DETAILS = "documentation"; + + private static final String ECORE_PACKAGE_NAME = "ecore"; + + private static final String ID_COLUMN_NAME = "Id"; + private static final int ID_COLUMN_WIDTH = 18; + + private static final String REF_COLUMN_PREFIX = "ref_"; + + /* + * (non-Javadoc) + * @see org.gecko.emf.exporter.EMFExporter#exportResourceTo(org.eclipse.emf.ecore.resource.Resource, java.io.OutputStream, java.util.Map) + */ + @Override + public void exportResourceTo(Resource resource, OutputStream outputStream, Map options) + throws EMFExportException { + Objects.requireNonNull(resource, "Resource is required for export!"); + + try { + + exportEObjectsTo(resource.getContents(), outputStream, options); + + } catch (Exception e) { + throw new EMFExportException(e); + } + } + + /* + * (non-Javadoc) + * @see org.gecko.emf.exporter.EMFExporter#exportEObjectsTo(java.util.List, java.io.OutputStream, java.util.Map) + */ + @Override + public void exportEObjectsTo(List eObjects, OutputStream outputStream, Map options) + throws EMFExportException { + Objects.requireNonNull(eObjects, "At least one EObject is required for export!"); + Objects.requireNonNull(outputStream, "Output stream is required for export!"); + + if (!eObjects.isEmpty()) { + + try { + + final Map exportOptions = validateExportOptions(options); + + SpreadSheet document = new SpreadSheet(); + + // maps sheet names to instances of sheets + final Map eClassesSheets = new HashMap(); + + // maps EObjects' unique identifiers (obtained from their hash codes) to + // instances of sheets, so those can be looked up e.g. when constructing links + final Map eObjectsSheets = new HashMap(); + + // stores EObjects' EClasses - used e.g. to construct meta data + final Set eObjectsClasses = new HashSet(); + + // stores EEnums - used e.g. to construct meta data + final Set eObjectsEnums = new HashSet(); + + // maps EObjects' unique identifiers to pseudo IDs - for those EObjects which + // lack id field + final Map eObjectsPseudoIDs = new HashMap(); + + final List eObjectsSafeCopy = safeCopy(eObjects); + + // pseudo IDs are needed before main processing starts + generatePseudoIDs(eObjectsSafeCopy, eObjectsPseudoIDs); + + // @formatter:off + createSheets(document, + eClassesSheets, + eObjectsClasses, + eObjectsEnums, + eObjectsPseudoIDs, + eObjectsSheets, + eObjectsSafeCopy, + exportOptions); + // @formatter:on + + // @formatter:off + createSheetsData(document, + eClassesSheets, + eObjectsClasses, + eObjectsEnums, + eObjectsPseudoIDs, + eObjectsSheets, + eObjectsSafeCopy, + exportOptions); + // @formatter:on + + if (exportMetadataEnabled(exportOptions)) { + exportMetadata(document, eObjectsClasses, eObjectsEnums, exportOptions); + } + + document.save(outputStream); + + } catch (Exception e) { + throw new EMFExportException(e); + } + } + } + + private void generatePseudoIDs(List eObjects, Map eObjectsPseudoIDs) { + final Set processedEObjectsIdentifiers = new HashSet(); + + generatePseudoIDs(eObjects, processedEObjectsIdentifiers, eObjectsPseudoIDs); + } + + private void generatePseudoIDs(List eObjects, Set processedEObjectsIdentifiers, + Map eObjectsPseudoIDs) { + for (EObject eObject : eObjects) { + if (isProcessed(processedEObjectsIdentifiers, eObject)) { + continue; + } + + processedEObjectsIdentifiers.add(getEObjectIdentifier(eObject)); + + generatePseudoID(eObject, eObjectsPseudoIDs); + + eObject.eClass().getEAllReferences().stream().forEach(eReference -> { + generatePseudoID(eObject, eReference, processedEObjectsIdentifiers, eObjectsPseudoIDs); + }); + } + } + + @SuppressWarnings("unchecked") + private void generatePseudoID(EObject eObject, EReference eReference, Set processedEObjectsIdentifiers, + Map eObjectsPseudoIDs) { + Object value = eObject.eGet(eReference); + if (value != null) { + if (!eReference.isMany() && value instanceof EObject) { + generatePseudoID((EObject) value, eObjectsPseudoIDs); + } else if (eReference.isMany()) { + generatePseudoIDs((List) value, processedEObjectsIdentifiers, eObjectsPseudoIDs); + } + } + } + + private void generatePseudoID(EObject eObject, Map eObjectsPseudoIDs) { + if (!hasID(eObject, eObjectsPseudoIDs)) { + eObjectsPseudoIDs.put(getEObjectIdentifier(eObject), UUID.randomUUID().toString()); + } + } + + private boolean hasID(EObject eObject, Map eObjectsPseudoIDs) { + return (hasID(eObject) || hasPseudoID(eObject, eObjectsPseudoIDs)); + } + + private boolean hasID(EObject eObject) { + return (getID(eObject) != null); + } + + private String getID(EObject eObject) { + return EcoreUtil.getID(eObject); + } + + private boolean hasPseudoID(EObject eObject, Map eObjectsPseudoIDs) { + return (eObjectsPseudoIDs.containsKey(getEObjectIdentifier(eObject))); + } + + private String getPseudoID(EObject eObject, Map eObjectsPseudoIDs) { + return eObjectsPseudoIDs.get(getEObjectIdentifier(eObject)); + } + + private void createSheets(SpreadSheet document, Map eClassesSheets, Set eObjectsClasses, + Set eObjectsEnums, Map eObjectsPseudoIDs, Map eObjectsSheets, + List eObjects, Map exportOptions) { + + final Set processedEObjectsIdentifiers = new HashSet(); + + for (EObject eObject : eObjects) { + // @formatter:off + createSheetForEObjectWithEReferences(document, + eClassesSheets, + processedEObjectsIdentifiers, + eObjectsClasses, + eObjectsEnums, + eObjectsPseudoIDs, + eObjectsSheets, + exportOptions, + eObject); + // @formatter:on + } + } + + private void createSheetsData(SpreadSheet document, Map eClassesSheets, Set eObjectsClasses, + Set eObjectsEnums, Map eObjectsPseudoIDs, Map eObjectsSheets, + List eObjects, Map exportOptions) throws EMFExportException { + + final Set processedEObjectsIdentifiers = new HashSet(); + + for (EObject eObject : eObjects) { + // @formatter:off + createSheetDataForEObjectWithEReferences(document, + eClassesSheets, + processedEObjectsIdentifiers, + eObjectsClasses, + eObjectsEnums, + eObjectsPseudoIDs, + eObjectsSheets, + exportOptions, + eObject); + // @formatter:on + } + } + + private void createSheetForEObjectWithEReferences(SpreadSheet document, Map eClassesSheets, + Set eObjectsIdentifiers, Set eObjectsClasses, Set eObjectsEnums, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions, EObject eObject) { + createSheet(document, eClassesSheets, eObjectsIdentifiers, eObjectsClasses, eObjectsEnums, eObjectsPseudoIDs, + eObjectsSheets, exportOptions, eObject); + + eObject.eClass().getEAllReferences().stream().forEach(r -> { + createSheetForEReference(document, eClassesSheets, eObjectsIdentifiers, eObjectsClasses, eObjectsEnums, + eObjectsPseudoIDs, eObjectsSheets, exportOptions, eObject, r); + }); + } + + private void createSheetDataForEObjectWithEReferences(SpreadSheet document, Map eClassesSheets, + Set eObjectsIdentifiers, Set eObjectsClasses, Set eObjectsEnums, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions, EObject eObject) throws EMFExportException { + createSheetData(document, eClassesSheets, eObjectsIdentifiers, eObjectsPseudoIDs, eObjectsSheets, exportOptions, + eObject); + + eObject.eClass().getEAllReferences().stream().forEach(r -> { + try { + createSheetDataForEReference(document, eClassesSheets, eObjectsIdentifiers, eObjectsPseudoIDs, + eObjectsSheets, exportOptions, eObject, r); + } catch (EMFExportException e) { + e.printStackTrace(); + } + }); + } + + private void createSheet(SpreadSheet document, Map eClassesSheets, Set eObjectsIdentifiers, + Set eObjectsClasses, Set eObjectsEnums, Map eObjectsPseudoIDs, + Map eObjectsSheets, Map exportOptions, EObject... eObjects) { + if ((eObjects.length > 0) && !isProcessed(eObjectsIdentifiers, eObjects[0])) { + EClass eClass = eObjects[0].eClass(); + + Sheet sheet = getOrAddSheet(document, eClassesSheets, eClass, eObjectsEnums, + hasPseudoID(eObjects[0], eObjectsPseudoIDs), exportOptions); + + for (EObject eObject : eObjects) { + Integer eObjectIdentifier = Integer.valueOf(getEObjectIdentifier(eObject)); + + eObjectsIdentifiers.add(eObjectIdentifier); + eObjectsClasses.add(eObject.eClass()); + eObjectsSheets.put(eObjectIdentifier, sheet); + + eObject.eClass().getEAllReferences().stream().forEach(r -> { + createSheetForEReference(document, eClassesSheets, eObjectsIdentifiers, eObjectsClasses, + eObjectsEnums, eObjectsPseudoIDs, eObjectsSheets, exportOptions, eObject, r); + }); + } + } + } + + @SuppressWarnings("unchecked") + private void createSheetForEReference(SpreadSheet document, Map eClassesSheets, + Set eObjectsIdentifiers, Set eObjectsClasses, Set eObjectsEnums, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions, EObject eObject, EReference r) { + if (!exportNonContainmentEnabled(exportOptions) && !r.isContainment()) { + return; + } + + Object value = eObject.eGet(r); + + if (value != null) { + if (!r.isMany() && value instanceof EObject) { + createSheet(document, eClassesSheets, eObjectsIdentifiers, eObjectsClasses, eObjectsEnums, + eObjectsPseudoIDs, eObjectsSheets, exportOptions, (EObject) value); + } else if (r.isMany()) { + createSheet(document, eClassesSheets, eObjectsIdentifiers, eObjectsClasses, eObjectsEnums, + eObjectsPseudoIDs, eObjectsSheets, exportOptions, + ((List) value).toArray(EObject[]::new)); + } + } + } + + private void createSheetData(SpreadSheet document, Map eClassesSheets, + Set eObjectsIdentifiers, Map eObjectsPseudoIDs, + Map eObjectsSheets, Map exportOptions, EObject... eObjects) + throws EMFExportException { + if ((eObjects.length > 0) && !isProcessed(eObjectsIdentifiers, eObjects[0])) { + EClass eClass = eObjects[0].eClass(); + + Sheet sheet = getSheet(eClassesSheets, eClass); + + for (EObject eObject : eObjects) { + eObjectsIdentifiers.add(getEObjectIdentifier(eObject)); + + createSheetData(document, sheet, eObject, eObjectsPseudoIDs, eObjectsSheets, exportOptions); + + eObject.eClass().getEAllReferences().stream().forEach(r -> { + try { + createSheetDataForEReference(document, eClassesSheets, eObjectsIdentifiers, eObjectsPseudoIDs, + eObjectsSheets, exportOptions, eObject, r); + } catch (EMFExportException e) { + e.printStackTrace(); + } + }); + } + } + } + + @SuppressWarnings("unchecked") + private void createSheetDataForEReference(SpreadSheet document, Map eClassesSheets, + Set eObjectsIdentifiers, Map eObjectsPseudoIDs, + Map eObjectsSheets, Map exportOptions, EObject eObject, EReference r) + throws EMFExportException { + if (!exportNonContainmentEnabled(exportOptions) && !r.isContainment()) { + return; + } + + Object value = eObject.eGet(r); + + if (value != null) { + if (!r.isMany() && value instanceof EObject) { + createSheetData(document, eClassesSheets, eObjectsIdentifiers, eObjectsPseudoIDs, eObjectsSheets, + exportOptions, (EObject) value); + } else if (r.isMany()) { + createSheetData(document, eClassesSheets, eObjectsIdentifiers, eObjectsPseudoIDs, eObjectsSheets, + exportOptions, ((List) value).toArray(EObject[]::new)); + } + } + } + + private void createSheetData(SpreadSheet document, Sheet sheet, EObject eObject, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions) { + + sheet.appendRow(); + + Range sheetDataRow = sheet.getRange((sheet.getMaxRows() - 1), 0, 1, sheet.getMaxColumns()); + + List eAllStructuralFeatures = eObject.eClass().getEAllStructuralFeatures(); + + int columnsCount = eAllStructuralFeatures.size(); + + for (int colIndex = 0; colIndex < columnsCount; colIndex++) { + + createSheetDataCell(document, sheetDataRow, colIndex, eObject, eAllStructuralFeatures.get(colIndex), + eObjectsPseudoIDs, eObjectsSheets, exportOptions); + } + + if (hasPseudoID(eObject, eObjectsPseudoIDs)) { + setStringValueCell(sheetDataRow, columnsCount, eObjectsPseudoIDs.get(getEObjectIdentifier(eObject))); + } + } + + private Sheet getOrAddSheet(SpreadSheet document, Map eClassesSheets, EClass eClass, + Set eObjectsEnums, boolean hasPseudoID, Map exportOptions) { + String sheetName = constructEClassSheetName(eClass); + + boolean sheetExists = eClassesSheets.containsKey(sheetName); + + Sheet sheet; + if (sheetExists) { + sheet = eClassesSheets.get(sheetName); + } else { + sheet = new Sheet(sheetName); + document.appendSheet(sheet); + eClassesSheets.put(sheetName, sheet); + + createSheetHeader(sheet, eClass, eObjectsEnums, hasPseudoID, exportOptions); + + if (freezeHeaderRowEnabled(exportOptions)) { + // TODO: freezing rows is currently not supported in SODS + // freezeTableHeader(document, sheet, 1, headersCount); + } + + if (exportMetadataEnabled(exportOptions)) { + addMetadataSheet(document, eClass); + } + } + + return sheet; + } + + private Sheet getSheet(Map eObjectsSheets, EClass eClass) throws EMFExportException { + String sheetName = constructEClassSheetName(eClass); + + if (!eObjectsSheets.containsKey(sheetName)) { + throw new EMFExportException("Sheet '" + sheetName + "' does not exist!"); + } + + return eObjectsSheets.get(sheetName); + } + + private void createSheetHeader(Sheet sheet, EClass eClass, Set eObjectsEnums, boolean hasPseudoID, + Map exportOptions) { + + List eAllStructuralFeatures = eClass.getEAllStructuralFeatures(); + + int columnsCount = eAllStructuralFeatures.size(); + + sheet.appendColumns((hasPseudoID ? (columnsCount + 1) : columnsCount) - 1); // newly created sheet already has + // one column + + Range sheetHeaderRow = sheet.getRange(0, 0, 1, sheet.getMaxColumns()); // newly created sheet already has one + // row + sheetHeaderRow.setStyle(HEADER_STYLE); + + for (int colIndex = 0; colIndex < columnsCount; colIndex++) { + + EStructuralFeature eStructuralFeature = eAllStructuralFeatures.get(colIndex); + + if (isEcoreEEnumDataType(eStructuralFeature)) { + eObjectsEnums.add(extractEEnumDataType(eStructuralFeature)); + } + + createSheetHeaderCell(sheetHeaderRow, eStructuralFeature, exportOptions, colIndex); + } + + if (hasPseudoID) { + createSheetHeaderCell(sheetHeaderRow, "id", exportOptions, columnsCount); + } + } + + private void createSheetHeaderCell(Range sheetHeaderRow, EStructuralFeature eStructuralFeature, + Map exportOptions, int colIndex) { + String sheetHeaderName = constructSheetHeaderName(eStructuralFeature); + + createSheetHeaderCell(sheetHeaderRow, sheetHeaderName, exportOptions, colIndex); + } + + private void createSheetHeaderCell(Range sheetHeaderRow, String sheetHeaderName, Map exportOptions, + int colIndex) { + + createSheetHeaderCell(sheetHeaderRow, colIndex, sheetHeaderName); + + if (adjustColumnWidthEnabled(exportOptions)) { + adjustColumnWidth(sheetHeaderRow.getSheet(), colIndex, adjustColumnWidthCharsCount(sheetHeaderName)); + } + } + + private int adjustColumnWidthCharsCount(String sheetHeaderName) { + if (sheetHeaderName.equalsIgnoreCase(ID_COLUMN_NAME) || sheetHeaderName.endsWith(ID_COLUMN_NAME) + || sheetHeaderName.startsWith(REF_COLUMN_PREFIX)) { + return ID_COLUMN_WIDTH; + } else { + return sheetHeaderName.length(); + } + } + + private void createSheetHeaderCell(Range sheetHeaderRow, int colIndex, String sheetHeaderName) { + sheetHeaderRow.getCell(0, colIndex).setValue(sheetHeaderName); + } + + private void adjustColumnWidth(Sheet sheet, int colIndex, int charsCount) { + if (sheet.getColumnWidth(colIndex) == null) { + sheet.setColumnWidth(colIndex, calculateColumnWidth(charsCount)); + } + } + + private Double calculateColumnWidth(int charsCount) { + return (Double.valueOf(charsCount) * (charsCount > 3 ? 5 : 7.5)); + } + + private void adjustRowHeight(Sheet sheet, int rowIndex, String value) { + sheet.setRowHeight(rowIndex, calculateRowHeight(value)); + } + + private Double calculateRowHeight(String value) { + return (Double.valueOf(value.length() / MAX_CHAR_PER_LINE_DEFAULT) * 5); + } + + + @SuppressWarnings("unused") + private void freezeSheetHeaderRow(Sheet sheet, int rowCount, int colCount) { + // TODO: freezing rows is currently not supported in SODS + } + + private String constructSheetHeaderName(EStructuralFeature eStructuralFeature) { + StringBuilder sb = new StringBuilder(100); + if (eStructuralFeature instanceof EReference) { + sb.append(REF_COLUMN_PREFIX); + } + sb.append(eStructuralFeature.getName()); + return sb.toString(); + } + + private boolean isEcoreEEnumDataType(EStructuralFeature eStructuralFeature) { + return (eStructuralFeature instanceof EAttribute + && ((EAttribute) eStructuralFeature).getEAttributeType() instanceof EEnum); + } + + private EEnum extractEEnumDataType(EStructuralFeature eStructuralFeature) { + return ((EEnum) ((EAttribute) eStructuralFeature).getEAttributeType()); + } + + private void createSheetDataCell(SpreadSheet document, Range sheetDataRow, int colIndex, EObject eObject, + EStructuralFeature eStructuralFeature, Map eObjectsPseudoIDs, + Map eObjectsSheets, Map exportOptions) { + if (eStructuralFeature instanceof EAttribute) { + EAttribute eAttribute = (EAttribute) eStructuralFeature; + + Object value = eObject.eGet(eAttribute); + if (value != null) { + if (!eAttribute.isMany()) { + + if (value instanceof Date) { + setDateValueCell(sheetDataRow, colIndex, (Date) value); + + } else if (value instanceof Number) { + setNumberValueCell(sheetDataRow, colIndex, (Number) value); + + } else if (value instanceof Boolean) { + setBooleanValueCell(sheetDataRow, colIndex, (Boolean) value); + + } else if (value instanceof byte[]) { + // TODO: clarify how byte arrays should be handled + setStringValueCell(sheetDataRow, colIndex, "EAttribute: byte[]"); + + } else { + setStringValueCell(sheetDataRow, colIndex, + EcoreUtil.convertToString(eAttribute.getEAttributeType(), value)); + } + + } else { + setMultiValueCell(sheetDataRow, colIndex, eAttribute, value); + } + + } else { + setVoidValueCell(sheetDataRow, colIndex); + } + + } else if (eStructuralFeature instanceof EReference) { + EReference eReference = (EReference) eStructuralFeature; + + setEReferenceValueCell(document, sheetDataRow, colIndex, eObject, eReference, eObjectsPseudoIDs, + eObjectsSheets, exportOptions); + } + } + + @SuppressWarnings("unchecked") + private void setEReferenceValueCell(SpreadSheet document, Range sheetDataRow, int colIndex, EObject eObject, + EReference r, Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions) { + Object value = eObject.eGet(r); + + if (value != null) { + if ((!r.isMany() && value instanceof EObject) || (r.isMany() && ((List) value).size() == 1)) { + setOneEReferenceValueCell(sheetDataRow, colIndex, + (!r.isMany() ? ((EObject) value) : ((List) value).get(0)), eObjectsPseudoIDs, + eObjectsSheets, exportOptions); + + } else if (r.isMany() && !((List) value).isEmpty()) { + if (addMappingTableEnabled(exportOptions)) { + createEReferencesMappingTable(document, sheetDataRow, colIndex, eObject, r, ((List) value), + eObjectsPseudoIDs, eObjectsSheets, exportOptions); + + } else { + setManyEReferencesValueCell(sheetDataRow, colIndex, ((List) value), eObjectsPseudoIDs, + eObjectsSheets, exportOptions); + } + } + } + } + + private void createEReferencesMappingTable(SpreadSheet document, Range sheetDataRow, int colIndex, + EObject fromEObject, EReference toEReference, List toEObjects, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions) { + + Sheet mappingTableSheet = getOrAddMappingTableSheet(document, fromEObject.eClass(), toEReference.getName(), + toEReference.getEReferenceType(), exportOptions); + + setReferenceToMappingTable(mappingTableSheet, sheetDataRow, colIndex, exportOptions); + + createMappingTableSheetData(mappingTableSheet, fromEObject, toEObjects, eObjectsPseudoIDs, eObjectsSheets, + exportOptions); + } + + private void setReferenceToMappingTable(Sheet mappingTableSheet, Range sheetDataRow, int colIndex, + Map exportOptions) { + + if (generateLinksEnabled(exportOptions)) { + setLinkedIDEReferenceValueCell(sheetDataRow, colIndex, + constructMappingTableEReferenceValue(mappingTableSheet.getName()), mappingTableSheet); + } else { + setNonLinkedIDEReferenceValueCell(sheetDataRow, colIndex, + constructMappingTableEReferenceValue(mappingTableSheet.getName())); + } + } + + private String constructMappingTableEReferenceValue(String mappingTableSheetName) { + StringBuilder sb = new StringBuilder(100); + sb.append("See: "); + sb.append(mappingTableSheetName); + return sb.toString(); + } + + private void createMappingTableSheetData(Sheet mappingTableSheet, EObject fromEObject, List toEObjects, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions) { + + for (EObject toEObject : toEObjects) { + createMappingTableSheetDataRow(mappingTableSheet, fromEObject, toEObject, eObjectsPseudoIDs, eObjectsSheets, + exportOptions); + } + } + + private void createMappingTableSheetDataRow(Sheet sheet, EObject fromEObject, EObject toEObject, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions) { + + sheet.appendRow(); + + Range sheetDataRow = sheet.getRange((sheet.getMaxRows() - 1), 0, 1, sheet.getMaxColumns()); + + setOneEReferenceValueCell(sheetDataRow, 0, fromEObject, eObjectsPseudoIDs, eObjectsSheets, exportOptions); + + setOneEReferenceValueCell(sheetDataRow, 1, toEObject, eObjectsPseudoIDs, eObjectsSheets, exportOptions); + + } + + private String constructMappingTableSheetName(EClass fromEClass, String fromFieldName) { + StringBuilder sb = new StringBuilder(100); + sb.append(fromEClass.getName()); + sb.append("_"); + sb.append(fromFieldName); + sb.append(" "); + sb.append("( "); + sb.append(MAPPING_TABLE_SHEET_SUFFIX); + sb.append(" )"); + return sb.toString(); + } + + private Sheet getOrAddMappingTableSheet(SpreadSheet document, EClass fromEClass, String fromFieldName, + EClass toEClass, Map exportOptions) { + String mappingTableSheetName = constructMappingTableSheetName(fromEClass, fromFieldName); + + System.out.println("Mapping table sheet name: " + mappingTableSheetName); // TODO: remove this comment + + Sheet mappingTableSheet = document.getSheet(mappingTableSheetName); + if (mappingTableSheet == null) { + mappingTableSheet = new Sheet(mappingTableSheetName); + document.appendSheet(mappingTableSheet); + + createMappingTableSheetHeader(mappingTableSheet, fromEClass, toEClass, exportOptions); + } + + return mappingTableSheet; + } + + private void createMappingTableSheetHeader(Sheet sheet, EClass fromEClass, EClass toEClass, + Map exportOptions) { + + sheet.appendColumn(); // newly created sheet already has one column + + Range sheetHeaderRow = sheet.getRange(0, 0, 1, sheet.getMaxColumns()); // newly created sheet already has one + // row + + sheetHeaderRow.setStyle(HEADER_STYLE); + + createMappingTableSheetHeaderCell(sheetHeaderRow, fromEClass, exportOptions, 0); + + createMappingTableSheetHeaderCell(sheetHeaderRow, toEClass, exportOptions, 1); + } + + private void createMappingTableSheetHeaderCell(Range sheetHeaderRow, EClass eClass, + Map exportOptions, int colIndex) { + String mappingTableSheetHeaderName = constructMappingTableSheetHeaderName(eClass); + + sheetHeaderRow.getCell(0, colIndex).setValue(mappingTableSheetHeaderName); + + if (adjustColumnWidthEnabled(exportOptions)) { + adjustColumnWidth(sheetHeaderRow.getSheet(), colIndex, + adjustColumnWidthCharsCount(mappingTableSheetHeaderName)); + } + } + + private String constructMappingTableSheetHeaderName(EClass eClass) { + StringBuilder sb = new StringBuilder(100); + + EAttribute idAttribute = eClass.getEIDAttribute(); + + if (idAttribute == null || idAttribute.getName().equalsIgnoreCase(ID_COLUMN_NAME)) { + sb.append(WordUtils.uncapitalize(eClass.getName())); + sb.append(ID_COLUMN_NAME); + } else { + sb.append(idAttribute.getName()); + } + + return sb.toString(); + } + + private void setOneEReferenceValueCell(Range sheetDataRow, int colIndex, EObject eObject, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions) { + Integer eObjectIdentifier = Integer.valueOf(getEObjectIdentifier(eObject)); + + if (hasID(eObject)) { + if (generateLinksEnabled(exportOptions) && eObjectsSheets.containsKey(eObjectIdentifier)) { + setLinkedIDEReferenceValueCell(sheetDataRow, colIndex, getID(eObject), + eObjectsSheets.get(eObjectIdentifier)); + } else { + setNonLinkedIDEReferenceValueCell(sheetDataRow, colIndex, getID(eObject)); + } + } else if (hasPseudoID(eObject, eObjectsPseudoIDs)) { + if (generateLinksEnabled(exportOptions) && eObjectsSheets.containsKey(eObjectIdentifier)) { + setLinkedIDEReferenceValueCell(sheetDataRow, colIndex, getPseudoID(eObject, eObjectsPseudoIDs), + eObjectsSheets.get(eObjectIdentifier)); + } else { + setNonLinkedIDEReferenceValueCell(sheetDataRow, colIndex, getPseudoID(eObject, eObjectsPseudoIDs)); + } + } else { + setNoIDEReferenceValueCell(sheetDataRow, colIndex, eObject); + } + } + + private void setManyEReferencesValueCell(Range sheetDataRow, int colIndex, List eObjects, + Map eObjectsPseudoIDs, Map eObjectsSheets, + Map exportOptions) { + + StringBuilder sb = new StringBuilder(); + + List linkedValues = new ArrayList(); + + for (int i = 0; i < eObjects.size(); i++) { + EObject eObject = eObjects.get(i); + + Integer eObjectIdentifier = Integer.valueOf(getEObjectIdentifier(eObject)); + + if (hasID(eObject)) { + if (generateLinksEnabled(exportOptions) && eObjectsSheets.containsKey(eObjectIdentifier)) { + LinkedValue linkedValue = LinkedValue.builder().value(getID(eObject)) + .href(eObjectsSheets.get(eObjectIdentifier)).build(); + linkedValues.add(linkedValue); + } else { + sb.append(getID(eObject)); + } + } else if (hasPseudoID(eObject, eObjectsPseudoIDs)) { + if (generateLinksEnabled(exportOptions) && eObjectsSheets.containsKey(eObjectIdentifier)) { + LinkedValue linkedValue = LinkedValue.builder().value(getPseudoID(eObject, eObjectsPseudoIDs)) + .href(eObjectsSheets.get(eObjectIdentifier)).build(); + linkedValues.add(linkedValue); + } else { + sb.append(getPseudoID(eObject, eObjectsPseudoIDs)); + } + + } else { + sb.append("EReference: " + eObject.eClass().getName()); + } + + if (hasMoreElements(i, eObjects.size())) { + sb.append(System.lineSeparator()); + } + } + + if (generateLinksEnabled(exportOptions) && !linkedValues.isEmpty()) { + setCellLinkedValues(sheetDataRow, colIndex, linkedValues); + } else { + setStringValueCell(sheetDataRow, colIndex, sb.toString()); + } + } + + private void setCellLinkedValues(Range sheetDataRow, int colIndex, List linkedValues) { + sheetDataRow.getCell(0, colIndex).setLinkedValues(linkedValues); + } + + private boolean hasMoreElements(int currentIndex, int size) { + return (currentIndex + 1) < size; + } + + private void setLinkedIDEReferenceValueCell(Range sheetDataRow, int colIndex, String refId, Sheet refSheet) { + LinkedValue linkedValue = LinkedValue.builder().value(refId).href(refSheet).build(); + sheetDataRow.getCell(0, colIndex).addLinkedValue(linkedValue); + } + + private void setNonLinkedIDEReferenceValueCell(Range sheetDataRow, int colIndex, String refId) { + setStringValueCell(sheetDataRow, colIndex, refId); + } + + private void setNoIDEReferenceValueCell(Range sheetDataRow, int colIndex, EObject eObject) { + setStringValueCell(sheetDataRow, colIndex, "EReference: " + eObject.eClass().getName()); + } + + private void setStringValueCell(Range sheetDataRow, int colIndex, String value) { + sheetDataRow.getCell(0, colIndex).setValue(value); + + if (value.length() > MAX_CHAR_PER_LINE_DEFAULT) { + sheetDataRow.getCell(0, colIndex).setStyle(WRAPPED_DATA_CELL_STYLE); + + adjustRowHeight(sheetDataRow.getSheet(), sheetDataRow.getRow(), value); + } + } + + private void setDateValueCell(Range sheetDataRow, int colIndex, Date value) { + sheetDataRow.getCell(0, colIndex).setValue(value); + } + + private void setNumberValueCell(Range sheetDataRow, int colIndex, Number value) { + sheetDataRow.getCell(0, colIndex).setValue(value.floatValue()); + } + + private void setBooleanValueCell(Range sheetDataRow, int colIndex, Boolean value) { + sheetDataRow.getCell(0, colIndex).setValue(value.booleanValue()); + } + + @SuppressWarnings("unchecked") + private void setMultiValueCell(Range sheetDataRow, int colIndex, EAttribute eAttribute, Object multiValue) { + StringBuilder sb = new StringBuilder(); + + Collection values = (Collection) multiValue; + + if (!values.isEmpty()) { + Iterator valuesIt = values.iterator(); + + while (valuesIt.hasNext()) { + sb.append(EcoreUtil.convertToString(eAttribute.getEAttributeType(), valuesIt.next())); + + if (valuesIt.hasNext()) { + sb.append(System.lineSeparator()); + } + } + } + + setStringValueCell(sheetDataRow, colIndex, sb.toString()); + } + + private void setVoidValueCell(Range sheetDataRow, int colIndex) { + sheetDataRow.getCell(0, colIndex).clear(); + } + + @SuppressWarnings("unused") + private Locale locale(Map exportOptions) { + return ((Locale) exportOptions.getOrDefault(EMFExportOptions.OPTION_LOCALE, Locale.getDefault())); + } + + private boolean exportNonContainmentEnabled(Map exportOptions) { + return ((boolean) exportOptions.getOrDefault(EMFExportOptions.OPTION_EXPORT_NONCONTAINMENT, Boolean.FALSE)); + } + + private boolean exportMetadataEnabled(Map exportOptions) { + return ((boolean) exportOptions.getOrDefault(EMFExportOptions.OPTION_EXPORT_METADATA, Boolean.FALSE)); + } + + private boolean adjustColumnWidthEnabled(Map exportOptions) { + return ((boolean) exportOptions.getOrDefault(EMFExportOptions.OPTION_ADJUST_COLUMN_WIDTH, Boolean.FALSE)); + } + + private boolean generateLinksEnabled(Map exportOptions) { + return ((boolean) exportOptions.getOrDefault(EMFExportOptions.OPTION_GENERATE_LINKS, Boolean.FALSE)); + } + + private boolean addMappingTableEnabled(Map exportOptions) { + return ((boolean) exportOptions.getOrDefault(EMFExportOptions.OPTION_ADD_MAPPING_TABLE, Boolean.FALSE)); + } + + private boolean freezeHeaderRowEnabled(Map exportOptions) { + // TODO: freezing rows is currently not supported in SODS + // return ((boolean) + // exportOptions.getOrDefault(EMFExportOptions.OPTION_FREEZE_HEADER_ROW, + // Boolean.FALSE)); + return false; + } + + private Map validateExportOptions(Map options) { + if (options == null) { + return Collections.emptyMap(); + } else { + return Map.copyOf(options); + } + } + + private void addMetadataSheet(SpreadSheet document, EClass eClass) { + document.appendSheet(new Sheet(constructEClassMetadataSheetName(eClass))); + } + + private Sheet addMetadataSheet(SpreadSheet document, EEnum eEnum) { + Sheet metadataSheet = new Sheet(constructEEnumMetadataSheetName(eEnum)); + document.appendSheet(metadataSheet); + return metadataSheet; + } + + private void exportMetadata(SpreadSheet document, Set eClasses, Set eEnums, + Map exportOptions) { + exportEClassesMetadata(document, eClasses, exportOptions); + exportEEnumsMetadata(document, eEnums, exportOptions); + } + + private void exportEClassesMetadata(SpreadSheet document, Set eClasses, Map exportOptions) { + for (EClass eClass : eClasses) { + Sheet metadataSheet = document.getSheet(constructEClassMetadataSheetName(eClass)); + + maybeSetEClassMetadataDocumentation(metadataSheet, eClass, exportOptions); + + createEClassMetadataSheetHeader(metadataSheet, exportOptions); + + createEClassMetadataSheetData(metadataSheet, eClass); + } + } + + private void exportEEnumsMetadata(SpreadSheet document, Set eEnums, Map exportOptions) { + for (EEnum eEnum : eEnums) { + Sheet metadataSheet = addMetadataSheet(document, eEnum); + + maybeSetEEnumMetadataDocumentationValueCell(metadataSheet, eEnum, exportOptions); + + createEEnumMetadataSheetHeader(metadataSheet, exportOptions); + + createEEnumMetadataSheetData(metadataSheet, eEnum); + } + } + + private void maybeSetEClassMetadataDocumentation(Sheet metadataSheet, EClass eClass, + Map exportOptions) { + EAnnotation genModelAnnotation = eClass.getEAnnotation(DOCUMENTATION_GENMODEL_SOURCE); + if (genModelAnnotation != null) { + setTypeLevelMetadataDocumentation(metadataSheet, genModelAnnotation, exportOptions); + } + } + + private void setEClassMetadataDocumentationValueCell(Range metadataSheetDataRow, + Map exportOptions) { + metadataSheetDataRow.getCell(0, 0).setValue(METADATA_DOCUMENTATION_HEADER); + metadataSheetDataRow.getCell(0, 0).setStyle(HEADER_STYLE); + + if (adjustColumnWidthEnabled(exportOptions)) { + adjustColumnWidth(metadataSheetDataRow.getSheet(), 0, + adjustColumnWidthCharsCount(METADATA_DOCUMENTATION_HEADER)); + } + } + + private void maybeSetEEnumMetadataDocumentationValueCell(Sheet metadataSheet, EEnum eEnum, + Map exportOptions) { + EAnnotation genModelAnnotation = eEnum.getEAnnotation(DOCUMENTATION_GENMODEL_SOURCE); + if (genModelAnnotation != null) { + setTypeLevelMetadataDocumentation(metadataSheet, genModelAnnotation, exportOptions); + } + } + + private void setTypeLevelMetadataDocumentation(Sheet metadataSheet, EAnnotation genModelAnnotation, + Map exportOptions) { + Map genModelAnnotationDetails = genModelAnnotation.getDetails().map(); + + if (genModelAnnotationDetails.containsKey(DOCUMENTATION_GENMODEL_DETAILS)) { + + metadataSheet.appendColumn(); + + Range metadataSheetDocumentationRow = metadataSheet.getRange((metadataSheet.getMaxRows() - 1), 0, 1, 2); + + setEClassMetadataDocumentationValueCell(metadataSheetDocumentationRow, exportOptions); + + setMetadataDocumentationValueCell(metadataSheetDocumentationRow, 1, + genModelAnnotationDetails.get(DOCUMENTATION_GENMODEL_DETAILS)); + + metadataSheet.appendRow(); + } + } + + private void createEClassMetadataSheetHeader(Sheet metadataSheet, Map exportOptions) { + createMetadataSheetHeader(metadataSheet, METADATA_ECLASS_SHEET_HEADERS, exportOptions); + } + + private void createEEnumMetadataSheetHeader(Sheet metadataSheet, Map exportOptions) { + createMetadataSheetHeader(metadataSheet, METADATA_EENUM_SHEET_HEADERS, exportOptions); + } + + private void createMetadataSheetHeader(Sheet metadataSheet, List headers, + Map exportOptions) { + int columnsCount = headers.size(); + + metadataSheet.appendColumns(columnsCount - metadataSheet.getMaxColumns()); + + Range metadataSheetHeaderRow = metadataSheet.getRange((metadataSheet.getMaxRows() - 1), 0, 1, + metadataSheet.getMaxColumns()); + + metadataSheetHeaderRow.setStyle(HEADER_STYLE); + + for (int colIndex = 0; colIndex < metadataSheet.getMaxColumns(); colIndex++) { + createMetadataSheetHeaderCell(metadataSheetHeaderRow, colIndex, headers.get(colIndex), exportOptions); + } + } + + private void createMetadataSheetHeaderCell(Range metadataSheetHeaderRow, int colIndex, String headerName, + Map exportOptions) { + metadataSheetHeaderRow.getCell(0, colIndex).setValue(headerName); + + if (adjustColumnWidthEnabled(exportOptions)) { + adjustColumnWidth(metadataSheetHeaderRow.getSheet(), colIndex, adjustColumnWidthCharsCount(headerName)); + } + } + + private void createEClassMetadataSheetData(Sheet metadataSheet, EClass eClass) { + eClass.getEAnnotations(); // TODO: clarify regarding instance-level docs + + eClass.getEAllStructuralFeatures().forEach(eStructuralFeature -> { + createEClassMetadataSheetDataRow(metadataSheet, eStructuralFeature); + }); + } + + private void createEEnumMetadataSheetData(Sheet metadataSheet, EEnum eEnum) { + eEnum.getEAnnotations(); // TODO: clarify regarding instance-level docs + + eEnum.getELiterals().forEach(eEnumLiteral -> { + createEEnumMetadataSheetDataRow(metadataSheet, eEnumLiteral); + }); + } + + private void createEClassMetadataSheetDataRow(Sheet metadataSheet, EStructuralFeature eStructuralFeature) { + metadataSheet.appendRow(); + + for (int colIndex = 0; colIndex < metadataSheet.getMaxColumns(); colIndex++) { + Range metadataSheetDataRow = metadataSheet.getRange((metadataSheet.getMaxRows() - 1), 0, 1, + metadataSheet.getMaxColumns()); + + createEClassMetadataSheetDataCell(metadataSheetDataRow, colIndex, eStructuralFeature); + } + } + + private void createEEnumMetadataSheetDataRow(Sheet metadataSheet, EEnumLiteral eEnumLiteral) { + metadataSheet.appendRow(); + + for (int colIndex = 0; colIndex < metadataSheet.getMaxColumns(); colIndex++) { + Range metadataSheetDataRow = metadataSheet.getRange((metadataSheet.getMaxRows() - 1), 0, 1, + metadataSheet.getMaxColumns()); + + createEEnumMetadataSheetDataCell(metadataSheetDataRow, colIndex, eEnumLiteral); + } + } + + private void createEClassMetadataSheetDataCell(Range metadataSheetDataRow, int colIndex, + EStructuralFeature eStructuralFeature) { + switch (colIndex) { + case 0: // Name + setEClassMetadataNameValueCell(metadataSheetDataRow, colIndex, eStructuralFeature); + break; + case 1: // Type + setEClassMetadataTypeValueCell(metadataSheetDataRow, colIndex, eStructuralFeature); + break; + case 2: // isMany + setEClassMetadataIsManyValueCell(metadataSheetDataRow, colIndex, eStructuralFeature); + break; + case 3: // isRequired + setEClassMetadataIsRequiredValueCell(metadataSheetDataRow, colIndex, eStructuralFeature); + break; + case 4: // Default value + setEClassMetadataDefaultValueCell(metadataSheetDataRow, colIndex, eStructuralFeature); + break; + case 5: // Documentation + setEStructuralFeatureMetadataDocumentationValueCell(metadataSheetDataRow, colIndex, eStructuralFeature); + break; + } + } + + private void createEEnumMetadataSheetDataCell(Range metadataSheetDataRow, int colIndex, EEnumLiteral eEnumLiteral) { + switch (colIndex) { + case 0: // Name + setEEnumMetadataNameValueCell(metadataSheetDataRow, colIndex, eEnumLiteral); + break; + case 1: // Literal + setEEnumMetadataLiteralValueCell(metadataSheetDataRow, colIndex, eEnumLiteral); + break; + case 2: // Value + setEEnumMetadataValueValueCell(metadataSheetDataRow, colIndex, eEnumLiteral); + break; + case 3: // Documentation + setEEnumLiteralMetadataDocumentationValueCell(metadataSheetDataRow, colIndex, eEnumLiteral); + break; + } + } + + private void setEClassMetadataNameValueCell(Range metadataSheetDataRow, int colIndex, + EStructuralFeature eStructuralFeature) { + setStringValueCell(metadataSheetDataRow, colIndex, eStructuralFeature.getName()); + } + + private void setEClassMetadataTypeValueCell(Range metadataSheetDataRow, int colIndex, + EStructuralFeature eStructuralFeature) { + if (eStructuralFeature instanceof EAttribute) { + EAttribute eAttribute = (EAttribute) eStructuralFeature; + + setStringValueCell(metadataSheetDataRow, colIndex, + normalizeMetadataTypeEAttributeName(eAttribute.getEAttributeType())); + + } else if (eStructuralFeature instanceof EReference) { + EReference eReference = (EReference) eStructuralFeature; + + setStringValueCell(metadataSheetDataRow, colIndex, eReference.getEReferenceType().getName()); + + } else { + setVoidValueCell(metadataSheetDataRow, colIndex); + } + } + + private String normalizeMetadataTypeEAttributeName(EDataType eAttributeType) { + if (isEcoreDataType(eAttributeType)) { + String instanceClassName = eAttributeType.getInstanceClassName(); + try { + return Class.forName(instanceClassName).getSimpleName(); + } catch (ClassNotFoundException e) { + return instanceClassName; + } + } else { + return eAttributeType.getName(); + } + } + + private boolean isEcoreDataType(EDataType eAttributeType) { + return eAttributeType.getEPackage().getName().equalsIgnoreCase(ECORE_PACKAGE_NAME); + } + + private void setEClassMetadataIsManyValueCell(Range metadataSheetDataRow, int colIndex, + EStructuralFeature eStructuralFeature) { + setBooleanValueCell(metadataSheetDataRow, colIndex, eStructuralFeature.isMany()); + } + + private void setEClassMetadataIsRequiredValueCell(Range metadataSheetDataRow, int colIndex, + EStructuralFeature eStructuralFeature) { + setBooleanValueCell(metadataSheetDataRow, colIndex, eStructuralFeature.isRequired()); + } + + private void setEClassMetadataDefaultValueCell(Range metadataSheetDataRow, int colIndex, + EStructuralFeature eStructuralFeature) { + if (eStructuralFeature instanceof EAttribute) { + EAttribute eAttribute = (EAttribute) eStructuralFeature; + + if (eAttribute.getDefaultValue() != null) { + setStringValueCell(metadataSheetDataRow, colIndex, + EcoreUtil.convertToString(eAttribute.getEAttributeType(), eAttribute.getDefaultValue())); + return; + } + } + + setVoidValueCell(metadataSheetDataRow, colIndex); + } + + private void setEStructuralFeatureMetadataDocumentationValueCell(Range metadataSheetDataRow, int colIndex, + EStructuralFeature eStructuralFeature) { + EAnnotation genModelAnnotation = eStructuralFeature.getEAnnotation(DOCUMENTATION_GENMODEL_SOURCE); + + setMetadataDocumentationValueCell(metadataSheetDataRow, colIndex, genModelAnnotation); + } + + private void setEEnumMetadataNameValueCell(Range metadataSheetDataRow, int colIndex, EEnumLiteral eEnumLiteral) { + setStringValueCell(metadataSheetDataRow, colIndex, eEnumLiteral.getName()); + } + + private void setEEnumMetadataLiteralValueCell(Range metadataSheetDataRow, int colIndex, EEnumLiteral eEnumLiteral) { + setStringValueCell(metadataSheetDataRow, colIndex, eEnumLiteral.getLiteral()); + } + + private void setEEnumMetadataValueValueCell(Range metadataSheetDataRow, int colIndex, EEnumLiteral eEnumLiteral) { + setNumberValueCell(metadataSheetDataRow, colIndex, eEnumLiteral.getValue()); + } + + private void setEEnumLiteralMetadataDocumentationValueCell(Range metadataSheetDataRow, int colIndex, + EEnumLiteral eEnumLiteral) { + EAnnotation genModelAnnotation = eEnumLiteral.getEAnnotation(DOCUMENTATION_GENMODEL_SOURCE); + + setMetadataDocumentationValueCell(metadataSheetDataRow, colIndex, genModelAnnotation); + } + + private void setMetadataDocumentationValueCell(Range metadataSheetDataRow, int colIndex, + EAnnotation genModelAnnotation) { + if (genModelAnnotation != null) { + Map genModelAnnotationDetails = genModelAnnotation.getDetails().map(); + + if (genModelAnnotationDetails.containsKey(DOCUMENTATION_GENMODEL_DETAILS)) { + setMetadataDocumentationValueCell(metadataSheetDataRow, colIndex, + genModelAnnotationDetails.get(DOCUMENTATION_GENMODEL_DETAILS)); + } + } else { + setVoidValueCell(metadataSheetDataRow, colIndex); + } + } + + private void setMetadataDocumentationValueCell(Range metadataSheetDataRow, int colIndex, String documentation) { + setStringValueCell(metadataSheetDataRow, colIndex, documentation); + } + + private int getEObjectIdentifier(EObject eObject) { + return eObject.hashCode(); + } + + private String constructEClassSheetName(EClass eClass) { + return eClass.getName(); + } + + private String constructEEnumSheetName(EEnum eEnum) { + return eEnum.getName(); + } + + private String constructEClassMetadataSheetName(EClass eClass) { + return constructMetadataSheetName(constructEClassSheetName(eClass)); + } + + private String constructEEnumMetadataSheetName(EEnum eEnum) { + return constructMetadataSheetName(constructEEnumSheetName(eEnum)); + } + + private String constructMetadataSheetName(String metadataSheetName) { + StringBuilder sb = new StringBuilder(100); + sb.append(metadataSheetName); + sb.append(" "); + sb.append("( "); + sb.append(METADATA_SHEET_SUFFIX); + sb.append(" )"); + return sb.toString(); + } + + private boolean isProcessed(Set eObjectsIdentifiers, EObject eObject) { + return eObjectsIdentifiers.contains(getEObjectIdentifier(eObject)); + } + + private List safeCopy(List eObjects) { + return EcoreUtil.copyAll(eObjects).stream().collect(toList()); + } +} diff --git a/org.gecko.emf.exporter/.classpath b/org.gecko.emf.exporter/.classpath new file mode 100644 index 00000000..66e477cd --- /dev/null +++ b/org.gecko.emf.exporter/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.gecko.emf.exporter/.gitignore b/org.gecko.emf.exporter/.gitignore new file mode 100644 index 00000000..7fdbdef7 --- /dev/null +++ b/org.gecko.emf.exporter/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/bin_test/ diff --git a/org.gecko.emf.exporter/.project b/org.gecko.emf.exporter/.project new file mode 100644 index 00000000..c8937da8 --- /dev/null +++ b/org.gecko.emf.exporter/.project @@ -0,0 +1,23 @@ + + + org.gecko.emf.exporter + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/org.gecko.emf.exporter/.settings/org.eclipse.core.resources.prefs b/org.gecko.emf.exporter/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..e8cd65cb --- /dev/null +++ b/org.gecko.emf.exporter/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//.settings/org.eclipse.core.resources.prefs=UTF-8 +encoding//.settings/org.eclipse.jdt.core.prefs=UTF-8 +encoding//.settings/org.eclipse.jdt.ui.prefs=UTF-8 +encoding/bnd.bnd=UTF-8 diff --git a/org.gecko.emf.exporter/.settings/org.eclipse.jdt.core.prefs b/org.gecko.emf.exporter/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..f2525a8b --- /dev/null +++ b/org.gecko.emf.exporter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/org.gecko.emf.exporter/bnd.bnd b/org.gecko.emf.exporter/bnd.bnd new file mode 100644 index 00000000..4c2c11a2 --- /dev/null +++ b/org.gecko.emf.exporter/bnd.bnd @@ -0,0 +1,9 @@ +Bundle-Version: 1.0.0.SNAPSHOT +Bundle-Name: Gecko EMF Exporter +Bundle-Description: Exporter for EMF + +-library: enable-emf + +-buildpath: \ + osgi.annotation;version='7.0',\ + osgi.core;version='7.0' \ No newline at end of file diff --git a/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExportException.java b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExportException.java new file mode 100644 index 00000000..71383db1 --- /dev/null +++ b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExportException.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2012 - 2023 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.exporter; + +/** + * Exception which may be thrown during EMF export. + * + * @author Michal H. Siemaszko + */ +public class EMFExportException extends Exception { + + /** serialVersionUID */ + private static final long serialVersionUID = 8114199181901603984L; + + public EMFExportException(String msg) { + super(msg); + } + + public EMFExportException(Throwable cause) { + super(cause); + } +} diff --git a/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExportOptions.java b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExportOptions.java new file mode 100644 index 00000000..18a4754b --- /dev/null +++ b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExportOptions.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2012 - 2023 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.exporter; + +/** + * Defines export options which can be passed via options map. + * + * @author Michal H. Siemaszko + */ +public interface EMFExportOptions { + + // locale to use + String OPTION_LOCALE = "LOCALE"; + + // export non-containment references, in addition to containment references (exported by default) + String OPTION_EXPORT_NONCONTAINMENT = "EXPORT_NONCONTAINMENT"; + + // extract and export metadata as additional sheets + String OPTION_EXPORT_METADATA = "EXPORT_METADATA"; + + // automatically adjust column width based on contents + String OPTION_ADJUST_COLUMN_WIDTH = "ADJUST_COLUMN_WIDTH"; + + // generate links for references + String OPTION_GENERATE_LINKS = "GENERATE_LINKS"; + + // generate mapping table + String OPTION_ADD_MAPPING_TABLE = "ADD_MAPPING_TABLE"; + + // TODO: freezing rows is currently not supported in SODS + // freeze header row +// String OPTION_FREEZE_HEADER_ROW = "FREEZE_HEADER_ROW"; + +} diff --git a/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExporter.java b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExporter.java new file mode 100644 index 00000000..34ebfc1e --- /dev/null +++ b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/EMFExporter.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2012 - 2023 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.exporter; + +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; + +/** + * Defines methods for exporting EMF resources and lists of EMF objects. + * + * @author Michal H. Siemaszko + */ +public interface EMFExporter { + + void exportResourceTo(Resource resource, OutputStream outputStream, Map options) throws EMFExportException; + + void exportEObjectsTo(List eObjects, OutputStream outputStream, Map options) + throws EMFExportException; +} diff --git a/org.gecko.emf.exporter/src/org/gecko/emf/exporter/package-info.java b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/package-info.java new file mode 100644 index 00000000..8ea3d518 --- /dev/null +++ b/org.gecko.emf.exporter/src/org/gecko/emf/exporter/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package org.gecko.emf.exporter; diff --git a/org.gecko.emf.json/bnd.bnd b/org.gecko.emf.json/bnd.bnd index afbb9a5d..c57c0b41 100644 --- a/org.gecko.emf.json/bnd.bnd +++ b/org.gecko.emf.json/bnd.bnd @@ -10,4 +10,4 @@ Bundle-Description: Extension to save and load EMF objects in/to JSON using Jack com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version=latest,\ com.fasterxml.jackson.dataformat.jackson-dataformat-properties;version=latest,\ org.eclipse.emfcloud.emfjson-jackson;version=latest,\ - com.fasterxml.jackson.datatype.jackson-datatype-jsr310;version=latest \ No newline at end of file + com.fasterxml.jackson.datatype.jackson-datatype-jsr310;version=latest diff --git a/org.gecko.emf.ods.tests/.classpath b/org.gecko.emf.ods.tests/.classpath new file mode 100644 index 00000000..66e477cd --- /dev/null +++ b/org.gecko.emf.ods.tests/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.gecko.emf.ods.tests/.gitignore b/org.gecko.emf.ods.tests/.gitignore new file mode 100644 index 00000000..7fdbdef7 --- /dev/null +++ b/org.gecko.emf.ods.tests/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/bin_test/ diff --git a/org.gecko.emf.ods.tests/.project b/org.gecko.emf.ods.tests/.project new file mode 100644 index 00000000..9fc82af5 --- /dev/null +++ b/org.gecko.emf.ods.tests/.project @@ -0,0 +1,23 @@ + + + org.gecko.emf.ods.tests + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/org.gecko.emf.ods.tests/.settings/org.eclipse.core.resources.prefs b/org.gecko.emf.ods.tests/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..f2c16853 --- /dev/null +++ b/org.gecko.emf.ods.tests/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding/bnd.bnd=UTF-8 +encoding/test.bndrun=UTF-8 diff --git a/org.gecko.emf.ods.tests/.settings/org.eclipse.jdt.core.prefs b/org.gecko.emf.ods.tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..f2525a8b --- /dev/null +++ b/org.gecko.emf.ods.tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/org.gecko.emf.ods.tests/bnd.bnd b/org.gecko.emf.ods.tests/bnd.bnd new file mode 100644 index 00000000..2e3084da --- /dev/null +++ b/org.gecko.emf.ods.tests/bnd.bnd @@ -0,0 +1,15 @@ +-enable-junit5: true +-library: enable-emf + +javac.source: 11 +javac.target: 11 + +Bundle-Version: 1.0.0.SNAPSHOT + +-buildpath: \ + org.gecko.emf.osgi.component,\ + org.eclipse.emf.ecore.xmi,\ + org.eclipse.emf.ecore,\ + org.gecko.emf.osgi.example.model.basic,\ + org.gecko.emf.exporter;version=latest,\ + org.apache.commons.commons-text diff --git a/org.gecko.emf.ods.tests/src/org/gecko/emf/ods/tests/EMFODSResourceTest.java b/org.gecko.emf.ods.tests/src/org/gecko/emf/ods/tests/EMFODSResourceTest.java new file mode 100644 index 00000000..081e68f8 --- /dev/null +++ b/org.gecko.emf.ods.tests/src/org/gecko/emf/ods/tests/EMFODSResourceTest.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2012 - 2022 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.gecko.emf.ods.tests.helper.EMFODSResourceTestHelper.createBusinessPerson; +import static org.gecko.emf.ods.tests.helper.EMFODSResourceTestHelper.createFlintstonesFamily; +import static org.gecko.emf.ods.tests.helper.EMFODSResourceTestHelper.createSimpsonFamily; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.gecko.emf.exporter.EMFExportOptions; +import org.gecko.emf.ods.configuration.EMFODSResource; +import org.gecko.emf.osgi.example.model.basic.BasicFactory; +import org.gecko.emf.osgi.example.model.basic.BusinessPerson; +import org.gecko.emf.osgi.example.model.basic.Family; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.osgi.test.common.annotation.InjectService; +import org.osgi.test.common.service.ServiceAware; +import org.osgi.test.junit5.context.BundleContextExtension; +import org.osgi.test.junit5.service.ServiceExtension; + +/** + * EMF ODS Resource integration test. + * + * @author Michal H. Siemaszko + */ +@ExtendWith(BundleContextExtension.class) +@ExtendWith(ServiceExtension.class) +public class EMFODSResourceTest { + + @Test + public void testSaveODS(@InjectService(timeout = 2000) ServiceAware rsAware, + @InjectService(timeout = 2000) ServiceAware bfAware) throws Exception { + + assertNotNull(rsAware); + assertThat(rsAware.getServices()).hasSize(1); + ResourceSet resourceSet = rsAware.getService(); + assertNotNull(resourceSet); + + assertNotNull(bfAware); + assertThat(bfAware.getServices()).hasSize(1); + BasicFactory factoryImpl = bfAware.getService(); + assertNotNull(factoryImpl); + + Resource resource = resourceSet.createResource(URI.createURI("basicPackageExporterTest.ods")); + assertNotNull(resource); + assertTrue(resource instanceof EMFODSResource); + + Family simpsonFamily = createSimpsonFamily(factoryImpl); + resource.getContents().add(simpsonFamily); + + Family flintstonesFamily = createFlintstonesFamily(factoryImpl); + resource.getContents().add(flintstonesFamily); + + BusinessPerson businessPerson = createBusinessPerson(factoryImpl); + resource.getContents().add(businessPerson); + + Path filePath = Files.createTempFile("testBasicPackageExport", ".ods"); + + OutputStream fileOutputStream = Files.newOutputStream(filePath); + + // @formatter:off + resource.save(fileOutputStream, + Map.of( + EMFExportOptions.OPTION_LOCALE, Locale.GERMANY, + EMFExportOptions.OPTION_EXPORT_NONCONTAINMENT, true, + EMFExportOptions.OPTION_EXPORT_METADATA, true, + EMFExportOptions.OPTION_ADJUST_COLUMN_WIDTH, true, + EMFExportOptions.OPTION_GENERATE_LINKS, true, + EMFExportOptions.OPTION_ADD_MAPPING_TABLE, true + )); + // @formatter:on + } +} diff --git a/org.gecko.emf.ods.tests/src/org/gecko/emf/ods/tests/helper/EMFODSResourceTestHelper.java b/org.gecko.emf.ods.tests/src/org/gecko/emf/ods/tests/helper/EMFODSResourceTestHelper.java new file mode 100644 index 00000000..18ef905d --- /dev/null +++ b/org.gecko.emf.ods.tests/src/org/gecko/emf/ods/tests/helper/EMFODSResourceTestHelper.java @@ -0,0 +1,370 @@ +/** + * Copyright (c) 2012 - 2023 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.tests.helper; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +import org.apache.commons.text.RandomStringGenerator; +import org.gecko.emf.osgi.example.model.basic.Address; +import org.gecko.emf.osgi.example.model.basic.BasicFactory; +import org.gecko.emf.osgi.example.model.basic.BusinessPerson; +import org.gecko.emf.osgi.example.model.basic.ContactContextType; +import org.gecko.emf.osgi.example.model.basic.ContactType; +import org.gecko.emf.osgi.example.model.basic.EmployeeInfo; +import org.gecko.emf.osgi.example.model.basic.Family; +import org.gecko.emf.osgi.example.model.basic.GenderType; +import org.gecko.emf.osgi.example.model.basic.Person; +import org.gecko.emf.osgi.example.model.basic.PersonContact; +import org.gecko.emf.osgi.example.model.basic.Tag; + +/** + * EMF ODS Resource integration test helper. + * + * @author Michal H. Siemaszko + */ +public class EMFODSResourceTestHelper { + + public static Family createSimpsonFamily(BasicFactory bf) { + Family simpsonFamily = bf.createFamily(); + simpsonFamily.setId("Simpsons"); + + Address address = createSimpsonsAddress(bf); + + Person homerSimpson = createHomerSimpson(bf, address); + simpsonFamily.setFather(homerSimpson); + + Person margeSimpson = createMargeSimpson(bf, address); + simpsonFamily.setMother(margeSimpson); + + Person bartSimpson = createBartSimpson(bf, address); + simpsonFamily.getChildren().add(bartSimpson); + + Person lisaSimpson = createLisaSimpson(bf, address); + simpsonFamily.getChildren().add(lisaSimpson); + + Person maggieSimpson = createMaggieSimpson(bf, address); + simpsonFamily.getChildren().add(maggieSimpson); + + homerSimpson.getRelatives().add(margeSimpson); + homerSimpson.getRelatives().add(bartSimpson); + homerSimpson.getRelatives().add(lisaSimpson); + homerSimpson.getRelatives().add(maggieSimpson); + + margeSimpson.getRelatives().add(homerSimpson); + margeSimpson.getRelatives().add(bartSimpson); + margeSimpson.getRelatives().add(lisaSimpson); + margeSimpson.getRelatives().add(maggieSimpson); + + bartSimpson.getRelatives().add(homerSimpson); + bartSimpson.getRelatives().add(margeSimpson); + bartSimpson.getRelatives().add(lisaSimpson); + bartSimpson.getRelatives().add(maggieSimpson); + + lisaSimpson.getRelatives().add(homerSimpson); + lisaSimpson.getRelatives().add(margeSimpson); + lisaSimpson.getRelatives().add(bartSimpson); + lisaSimpson.getRelatives().add(maggieSimpson); + + maggieSimpson.getRelatives().add(homerSimpson); + maggieSimpson.getRelatives().add(margeSimpson); + maggieSimpson.getRelatives().add(lisaSimpson); + maggieSimpson.getRelatives().add(maggieSimpson); + + homerSimpson.getTags().add(createMultiLevelTag(bf, createUniquePrefix(10))); + + homerSimpson.setBigInt(BigInteger.TEN); + + homerSimpson.getBigDec().add(BigDecimal.ZERO); + homerSimpson.getBigDec().add(BigDecimal.ONE); + homerSimpson.getBigDec().add(BigDecimal.TEN); + + homerSimpson.setImage(createByteArr()); + + homerSimpson.getProperties().putAll(createProperties(createUniquePrefix(10))); + + return simpsonFamily; + } + + private static Address createSimpsonsAddress(BasicFactory bf) { + return createAddress(bf, "742 Evergreen Terrace", "Springfield", "97482"); + } + + private static Person createHomerSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Homer", "Simpson", GenderType.MALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createMargeSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Marge", "Simpson", GenderType.FEMALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createBartSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Bart", "Simpson", GenderType.MALE, address); + + return p; + } + + private static Person createLisaSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Lisa", "Simpson", GenderType.FEMALE, address); + + return p; + } + + private static Person createMaggieSimpson(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Maggie", "Simpson", GenderType.FEMALE, address); + + return p; + } + + public static Family createFlintstonesFamily(BasicFactory bf) { + Family flintstonesFamily = bf.createFamily(); + flintstonesFamily.setId("Flintstones"); + + Address address = createFlintstonesAddress(bf); + + Person fredFlintstone = createFredFlintstone(bf, address); + flintstonesFamily.setFather(fredFlintstone); + + Person wilmaFlintstone = createWilmaFlintstone(bf, address); + flintstonesFamily.setMother(wilmaFlintstone); + + Person pebblesFlintstone = createPebblesFlintstone(bf, address); + flintstonesFamily.getChildren().add(pebblesFlintstone); + + Person stonyFlintstone = createStonyFlintstone(bf, address); + flintstonesFamily.getChildren().add(stonyFlintstone); + + fredFlintstone.getRelatives().add(wilmaFlintstone); + fredFlintstone.getRelatives().add(pebblesFlintstone); + fredFlintstone.getRelatives().add(stonyFlintstone); + + wilmaFlintstone.getRelatives().add(fredFlintstone); + wilmaFlintstone.getRelatives().add(pebblesFlintstone); + wilmaFlintstone.getRelatives().add(stonyFlintstone); + + pebblesFlintstone.getRelatives().add(fredFlintstone); + pebblesFlintstone.getRelatives().add(wilmaFlintstone); + pebblesFlintstone.getRelatives().add(stonyFlintstone); + + stonyFlintstone.getRelatives().add(fredFlintstone); + stonyFlintstone.getRelatives().add(wilmaFlintstone); + stonyFlintstone.getRelatives().add(pebblesFlintstone); + + fredFlintstone.getTags().add(createMultiLevelTag(bf, createUniquePrefix(10))); + + fredFlintstone.setBigInt(BigInteger.TEN); + + fredFlintstone.getBigDec().add(BigDecimal.ZERO); + fredFlintstone.getBigDec().add(BigDecimal.ONE); + fredFlintstone.getBigDec().add(BigDecimal.TEN); + + fredFlintstone.setImage(createByteArr()); + + fredFlintstone.getProperties().putAll(createProperties(createUniquePrefix(10))); + + return flintstonesFamily; + } + + private static Address createFlintstonesAddress(BasicFactory bf) { + return createAddress(bf, "301 Cobblestone Way", "Bedrock", "70777"); + } + + private static Person createFredFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Fred", "Flintstone", GenderType.MALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createWilmaFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Wilma", "Flintstone", GenderType.FEMALE, address); + + p.getContact().add(createHomePhonePersonContact(bf, p)); + p.getContact().add(createHomeMobilePersonContact(bf, p)); + p.getContact().add(createHomeWhatsAppPersonContact(bf, p)); + p.getContact().add(createHomeEmailPersonContact(bf, p)); + p.getContact().add(createHomeSkypePersonContact(bf, p)); + p.getContact().add(createHomeWebAddressPersonContact(bf, p)); + + return p; + } + + private static Person createPebblesFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Pebbles", "Flintstone", GenderType.FEMALE, address); + + return p; + } + + private static Person createStonyFlintstone(BasicFactory bf, Address address) { + Person p = createPerson(bf, "Stony", "Flintstone", GenderType.MALE, address); + + return p; + } + + public static BusinessPerson createBusinessPerson(BasicFactory bf) { + BusinessPerson bp = bf.createBusinessPerson(); + + bp.setId(UUID.randomUUID().toString()); + bp.setFirstName("Thomas"); + bp.setLastName("Edison"); + bp.setGender(GenderType.MALE); + + bp.setCompanyIdCardNumber(UUID.randomUUID().toString()); + + EmployeeInfo nikolaTesla = bf.createEmployeeInfo(); + nikolaTesla.setPosition("one-time employee"); + bp.getEmployeeInfo().add(nikolaTesla); + + return bp; + } + + private static Person createPerson(BasicFactory bf, String firstName, String lastName, GenderType gender, + Address address) { + Person p = bf.createPerson(); + + p.setId(UUID.randomUUID().toString()); + p.setFirstName(firstName); + p.setLastName(lastName); + p.setGender(gender); + + p.setAddress(address); + + return p; + } + + private static Address createAddress(BasicFactory bf, String street, String city, String zip) { + Address a = bf.createAddress(); + + a.setId(UUID.randomUUID().toString()); + a.setStreet(street); + a.setCity(city); + a.setZip(zip); + + return a; + } + + private static PersonContact createHomePhonePersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.PHONE, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeMobilePersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.MOBILE, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeWhatsAppPersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.WHATSAPP, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeEmailPersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.EMAIL, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeSkypePersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.SKYPE, ContactContextType.HOME, UUID.randomUUID().toString(), p); + } + + private static PersonContact createHomeWebAddressPersonContact(BasicFactory bf, Person p) { + return createPersonContact(bf, ContactType.WEBADDRESS, ContactContextType.HOME, UUID.randomUUID().toString(), + p); + } + + private static PersonContact createPersonContact(BasicFactory bf, ContactType type, ContactContextType context, + String value, Person p) { + PersonContact pc = bf.createPersonContact(); + + pc.setContext(context); + pc.setType(type); + pc.setValue(value); + + pc.setContactPerson(p); + + return pc; + } + + private static Tag createMultiLevelTag(BasicFactory bf, String namePrefix) { + Tag t1 = createTag(bf, namePrefix, "tag_level_1", "tag_level_1_value", "tag_level_1_description"); + + t1.setTag(createTag(bf, namePrefix, "tag_level_2", "tag_level_2_value", "tag_level_2_description")); + + t1.getTags().add(createTag(bf, namePrefix, "tag_level_3", "tag_level_3_value", "tag_level_3_description")); + + return t1; + } + + private static Tag createTag(BasicFactory bf, String namePrefix, String name, String value, String description) { + Tag t = bf.createTag(); + + t.setName(namePrefix + "_" + name); + t.setValue(value); + t.setDescription(description); + + return t; + } + + private static byte[] createByteArr() { + byte[] b = new byte[20]; + new Random().nextBytes(b); + return b; + } + + private static Map createProperties(String namePrefix) { + Map props = new HashMap(); + + props.put(createPropertyName(namePrefix, "prop_1"), "prop_1_value"); + props.put(createPropertyName(namePrefix, "prop_2"), "prop_2_value"); + props.put(createPropertyName(namePrefix, "prop_3"), "prop_3_value"); + props.put(createPropertyName(namePrefix, "prop_4"), "prop_4_value"); + + return props; + } + + private static String createPropertyName(String prefix, String name) { + return (prefix + "_" + name); + } + + private static String createUniquePrefix(int maxChars) { + // @formatter:off + return new RandomStringGenerator.Builder() + .withinRange('a', 'z') + .build() + .generate(maxChars); + // @formatter:on + } +} diff --git a/org.gecko.emf.ods.tests/test.bndrun b/org.gecko.emf.ods.tests/test.bndrun new file mode 100644 index 00000000..82abedc2 --- /dev/null +++ b/org.gecko.emf.ods.tests/test.bndrun @@ -0,0 +1,55 @@ +-runfw: org.apache.felix.framework;version='[7.0.1,7.0.1]' +-runprovidedcapabilities: ${native_capability} + +-resolve.effective: active + +-runbundles.junit5: ${test.runbundles} + +-runbundles: \ + junit-jupiter-api;version='[5.8.2,5.8.3)',\ + junit-platform-commons;version='[1.8.2,1.8.3)',\ + org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ + org.apache.felix.scr;version='[2.1.30,2.1.31)',\ + org.eclipse.emf.common;version='[2.23.0,2.23.1)',\ + org.eclipse.emf.ecore;version='[2.25.0,2.25.1)',\ + org.eclipse.emf.ecore.xmi;version='[2.16.0,2.16.1)',\ + org.gecko.emf.osgi.api;version='[4.1.1,4.1.2)',\ + org.gecko.emf.osgi.component;version='[4.1.1,4.1.2)',\ + org.gecko.emf.osgi.example.model.basic;version='[4.1.1,4.1.2)',\ + org.opentest4j;version='[1.2.0,1.2.1)',\ + org.osgi.util.function;version='[1.1.0,1.1.1)',\ + org.osgi.util.pushstream;version='[1.0.1,1.0.2)',\ + org.apache.felix.converter;version='[1.0.18,1.0.19)',\ + org.osgi.util.promise;version='[1.2.0,1.2.1)',\ + assertj-core;version='[3.22.0,3.22.1)',\ + junit-jupiter-params;version='[5.8.2,5.8.3)',\ + org.osgi.test.common;version='[1.1.0,1.1.1)',\ + org.osgi.test.junit5;version='[1.1.0,1.1.1)',\ + com.fasterxml.jackson.core.jackson-annotations;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.core.jackson-core;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.core.jackson-databind;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.dataformat.jackson-dataformat-properties;version='[2.14.1,2.14.2)',\ + com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.14.1,2.14.2)',\ + de.undercouch.bson4jackson;version='[2.13.1,2.13.2)',\ + org.apache.commons.commons-text;version='[1.10.0,1.10.1)',\ + org.apache.commons.lang3;version='[3.12.0,3.12.1)',\ + org.eclipse.emfcloud.emfjson-jackson;version='[2.2.0,2.2.1)',\ + org.gecko.emf.exporter;version=snapshot,\ + org.gecko.emf.exporter.ods;version=snapshot,\ + org.gecko.emf.json;version=snapshot,\ + org.gecko.emf.ods;version=snapshot,\ + org.gecko.emf.ods.tests;version=snapshot,\ + org.yaml.snakeyaml;version='[1.33.0,1.33.1)',\ + com.github.miachm.sods;version='[1.5.3,1.5.4)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ + org.gecko.emf.pushstreams;version=snapshot + +-runrequires: bnd.identity;id='org.gecko.emf.ods.tests' + +-runee: JavaSE-11 + +-runtrace: true + +-runproperties.debug: \ + felix.log.level=4,\ + org.osgi.service.log.admin.loglevel=DEBUG diff --git a/org.gecko.emf.ods/.classpath b/org.gecko.emf.ods/.classpath new file mode 100644 index 00000000..66e477cd --- /dev/null +++ b/org.gecko.emf.ods/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.gecko.emf.ods/.gitignore b/org.gecko.emf.ods/.gitignore new file mode 100644 index 00000000..7fdbdef7 --- /dev/null +++ b/org.gecko.emf.ods/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/bin_test/ diff --git a/org.gecko.emf.ods/.project b/org.gecko.emf.ods/.project new file mode 100644 index 00000000..b41fd5ed --- /dev/null +++ b/org.gecko.emf.ods/.project @@ -0,0 +1,23 @@ + + + org.gecko.emf.ods + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/org.gecko.emf.ods/.settings/org.eclipse.core.resources.prefs b/org.gecko.emf.ods/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..1554c6f8 --- /dev/null +++ b/org.gecko.emf.ods/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/bnd.bnd=UTF-8 diff --git a/org.gecko.emf.ods/.settings/org.eclipse.jdt.core.prefs b/org.gecko.emf.ods/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..f2525a8b --- /dev/null +++ b/org.gecko.emf.ods/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/org.gecko.emf.ods/bnd.bnd b/org.gecko.emf.ods/bnd.bnd new file mode 100644 index 00000000..37693b99 --- /dev/null +++ b/org.gecko.emf.ods/bnd.bnd @@ -0,0 +1,8 @@ +Bundle-Version: 1.0.0.SNAPSHOT +Bundle-Name: Gecko EMF ODS Extension +Bundle-Description: Extension to save EMF objects to ODS using SODS + +-library: enable-emf + +-buildpath: \ + org.gecko.emf.exporter diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/EMFODSResourceFactoryConfigurator.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/EMFODSResourceFactoryConfigurator.java new file mode 100644 index 00000000..24f5952b --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/EMFODSResourceFactoryConfigurator.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2012 - 2022 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods; + +import org.eclipse.emf.ecore.resource.Resource.Factory.Registry; +import org.gecko.emf.exporter.EMFExporter; +import org.gecko.emf.ods.configuration.EMFODSResource; +import org.gecko.emf.ods.configuration.EMFODSResourceFactory; +import org.gecko.emf.ods.constants.EMFODSConstants; +import org.gecko.emf.osgi.ResourceFactoryConfigurator; +import org.gecko.emf.osgi.annotation.EMFResourceFactoryConfigurator; +import org.gecko.emf.osgi.annotation.provide.ProvideEMFResourceConfigurator; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Implementation of the {@link ResourceFactoryConfigurator} to provide support for {@link EMFODSResource}. + * + * It provides the {@link EMFODSResourceFactory} for the following identifiers: + *
    + *
  • Extension: ods + *
  • contentType: application/vnd.oasis.opendocument.spreadsheet + *
+ * + * @author Michal H. Siemaszko + */ +@Component(name = "EMFODSConfigurator", immediate = true, service = ResourceFactoryConfigurator.class) +//@formatter:off +@ProvideEMFResourceConfigurator( + name = EMFODSConstants.EMFODS_CAPABILITY_NAME, + contentType = { + EMFODSConstants.EMFODS_CONTENT_TYPE + }, + fileExtension = { + EMFODSConstants.EMFODS_FILE_EXTENSION + }, + version = "1.0.0" +) +@EMFResourceFactoryConfigurator( + name = EMFODSConstants.EMFODS_CAPABILITY_NAME, + fileExtension = { + EMFODSConstants.EMFODS_FILE_EXTENSION + }, + contentType = { + EMFODSConstants.EMFODS_CONTENT_TYPE + } + ) +//@formatter:on +public class EMFODSResourceFactoryConfigurator implements ResourceFactoryConfigurator { + + @Reference(target = ("(component.name=EMFODSExporter)")) + private EMFExporter emfODSExporter; + + /* + * (non-Javadoc) + * @see de.dim.emf.osgi.ResourceFactoryConfigurator#configureResourceFactory(org.eclipse.emf.ecore.resource.Resource.Factory.Registry) + */ + @Override + public void configureResourceFactory(Registry registry) { + registry.getExtensionToFactoryMap().put(EMFODSConstants.EMFODS_FILE_EXTENSION, createODSFactory()); + + registry.getContentTypeToFactoryMap().put(EMFODSConstants.EMFODS_CONTENT_TYPE, createODSFactory()); + } + + private EMFODSResourceFactory createODSFactory() { + return new EMFODSResourceFactory(emfODSExporter); + } + + /* + * (non-Javadoc) + * @see de.dim.emf.osgi.ResourceFactoryConfigurator#unconfigureResourceFactory(org.eclipse.emf.ecore.resource.Resource.Factory.Registry) + */ + @Override + public void unconfigureResourceFactory(Registry registry) { + registry.getExtensionToFactoryMap().remove(EMFODSConstants.EMFODS_FILE_EXTENSION); + + registry.getContentTypeToFactoryMap().remove(EMFODSConstants.EMFODS_CONTENT_TYPE); + } +} diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/annotation/RequireEMFODS.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/annotation/RequireEMFODS.java new file mode 100644 index 00000000..82e359cd --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/annotation/RequireEMFODS.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2012 - 2022 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.annotation; + +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.gecko.emf.ods.constants.EMFODSConstants; +import org.gecko.emf.osgi.EMFNamespaces; +import org.gecko.emf.osgi.ResourceSetConfigurator; +import org.gecko.emf.osgi.annotation.require.RequireEMF; +import org.osgi.annotation.bundle.Requirement; + +@Documented +@Retention(CLASS) +@Target({ TYPE, PACKAGE }) +@Requirement( + namespace = EMFNamespaces.EMF_CONFIGURATOR_NAMESPACE, + name = ResourceSetConfigurator.EMF_CONFIGURATOR_NAME, + filter = "(" + EMFNamespaces.EMF_CONFIGURATOR_NAME + "=" + EMFODSConstants.EMFODS_CAPABILITY_NAME + ")" + ) +@RequireEMF + +/** + * Meta annotation to generate a Require Capability for EMF ODS + * + * @author Michal H. Siemaszko + */ +public @interface RequireEMFODS { + +} diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/annotation/package-info.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/annotation/package-info.java new file mode 100644 index 00000000..6a4b3cee --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/annotation/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package org.gecko.emf.ods.annotation; diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/EMFODSResource.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/EMFODSResource.java new file mode 100644 index 00000000..4cb5aefb --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/EMFODSResource.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2012 - 2022 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.configuration; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.impl.ResourceImpl; +import org.gecko.emf.exporter.EMFExportException; +import org.gecko.emf.exporter.EMFExporter; + +/** + * A Resource implementation that writes its content in ODS. + * + * @author Michal H. Siemaszko + */ +public class EMFODSResource extends ResourceImpl { + private EMFExporter emfODSExporter; + + public EMFODSResource(URI uri) { + super(uri); + } + + public EMFODSResource(URI uri, EMFExporter emfODSExporter) { + super(uri); + + this.emfODSExporter = emfODSExporter; + } + + @Override + protected void doLoad(InputStream inputStream, Map options) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + protected void doSave(OutputStream outputStream, Map options) throws IOException { + if (options == null) { + options = Collections.emptyMap(); + } + + if (emfODSExporter != null) { + try { + emfODSExporter.exportResourceTo(this, outputStream, options); + } catch (EMFExportException e) { + throw new IOException(e); + } + } + } +} diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/EMFODSResourceFactory.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/EMFODSResourceFactory.java new file mode 100644 index 00000000..d71150b1 --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/EMFODSResourceFactory.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2012 - 2022 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.configuration; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.impl.ResourceFactoryImpl; +import org.gecko.emf.exporter.EMFExporter; + +/** + * {@link ResourceFactoryImpl} for the ODS resource. + * + * @author Michal H. Siemaszko + */ +public class EMFODSResourceFactory extends ResourceFactoryImpl { + private EMFExporter emfODSExporter; + + /** + * Creates a new instance. + */ + public EMFODSResourceFactory() { + super(); + } + + public EMFODSResourceFactory(EMFExporter emfODSExporter) { + super(); + + this.emfODSExporter = emfODSExporter; + } + + /* + * (non-Javadoc) + * @see org.eclipse.emf.ecore.resource.impl.ResourceFactoryImpl#createResource(org.eclipse.emf.common.util.URI) + */ + @Override + public Resource createResource(URI uri) { + if (emfODSExporter != null) { + return new EMFODSResource(uri, emfODSExporter); + } else { + return new EMFODSResource(uri); + } + } +} diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/package-info.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/package-info.java new file mode 100644 index 00000000..2215f704 --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/configuration/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package org.gecko.emf.ods.configuration; diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/constants/EMFODSConstants.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/constants/EMFODSConstants.java new file mode 100644 index 00000000..00b02bc1 --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/constants/EMFODSConstants.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2012 - 2022 Data In Motion and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Data In Motion - initial API and implementation + */ +package org.gecko.emf.ods.constants; + +/** + * Constants used as options during load/save operations on an EMF ODS Resource. + * + * @author Michal H. Siemaszko + */ +public interface EMFODSConstants { + + /** Constant for the general Capability Namespace **/ + static final String EMFODS_CAPABILITY_NAME = "EMFODS"; + + static final String EMFODS_FILE_EXTENSION = "ods"; + + static final String EMFODS_CONTENT_TYPE = "application/vnd.oasis.opendocument.spreadsheet"; +} \ No newline at end of file diff --git a/org.gecko.emf.ods/src/org/gecko/emf/ods/constants/package-info.java b/org.gecko.emf.ods/src/org/gecko/emf/ods/constants/package-info.java new file mode 100644 index 00000000..c155e878 --- /dev/null +++ b/org.gecko.emf.ods/src/org/gecko/emf/ods/constants/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package org.gecko.emf.ods.constants; diff --git a/org.gecko.emf.util.bnd.library.workspace/required.bndrun b/org.gecko.emf.util.bnd.library.workspace/required.bndrun index c3c2e1c8..feab185d 100644 --- a/org.gecko.emf.util.bnd.library.workspace/required.bndrun +++ b/org.gecko.emf.util.bnd.library.workspace/required.bndrun @@ -1,7 +1,6 @@ -runfw: org.apache.felix.framework -runee: JavaSE-11 - #example #org.bndtools.template;filter:='(collect=example)' -runrequires: \ @@ -9,7 +8,14 @@ bnd.identity;id='org.gecko.emf.collections',\ bnd.identity;id='org.gecko.emf.json',\ bnd.identity;id='org.gecko.emf.pushstreams',\ - bnd.identity;id='org.gecko.emf.util.model' + bnd.identity;id='org.gecko.emf.util.model',\ + bnd.identity;id='org.gecko.emf.ods',\ + bnd.identity;id='org.gecko.emf.exporter.ods' + +-runblacklist: \ + bnd.identity;id='ch.qos.logback.classic',\ + bnd.identity;id='ch.qos.logback.core' + -runbundles: \ org.apache.felix.configadmin;version='[1.9.22,1.9.23)',\ org.eclipse.emf.ecore.xmi;version='[2.16.0,2.16.1)',\ @@ -34,8 +40,12 @@ com.fasterxml.jackson.dataformat.jackson-dataformat-properties;version='[2.14.1,2.14.2)',\ com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.14.1,2.14.2)',\ org.eclipse.emfcloud.emfjson-jackson;version='[2.2.0,2.2.1)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)',\ org.yaml.snakeyaml;version='[1.33.0,1.33.1)',\ - com.fasterxml.jackson.datatype.jackson-datatype-jsr310;version='[2.14.1,2.14.2)' --runblacklist: \ - bnd.identity;id='ch.qos.logback.classic',\ - bnd.identity;id='ch.qos.logback.core' \ No newline at end of file + com.github.miachm.sods;version='[1.5.3,1.5.4)',\ + org.gecko.emf.exporter;version=snapshot,\ + org.gecko.emf.exporter.ods;version=snapshot,\ + org.gecko.emf.ods;version=snapshot,\ + org.apache.commons.commons-text;version='[1.10.0,1.10.1)',\ + org.apache.commons.lang3;version='[3.12.0,3.12.1)',\ + org.yaml.snakeyaml;version='[1.33.0,1.33.1)'