Bug 547636: Improve exporter view cleaning

Execute view validation and removal of invalid view model elements up to
five times during the JSON Forms export.

ViewModelElements determined to be invalid by the ViewValidator are
removed. However this might again lead to invalid view model elements
(usually the parent). Therefore the view model element validation and
removal is now executed multiple times. To avoid an endless loop this is
restricted to five iterations.

This commit also adds some JavaDoc to the ViewModelHelper.

Change-Id: Ia2631c2bc952bc49aba1f0bb98abca482c5ccd48
Signed-off-by: Stefan Dirix <sdirix@eclipsesource.com>
diff --git a/bundles/org.eclipse.emf.ecp.emf2web.json/src/org/eclipse/emf/ecp/emf2web/json/controller/xtend/ViewCleaner.xtend b/bundles/org.eclipse.emf.ecp.emf2web.json/src/org/eclipse/emf/ecp/emf2web/json/controller/xtend/ViewCleaner.xtend
index ab370d0..0fd1880 100644
--- a/bundles/org.eclipse.emf.ecp.emf2web.json/src/org/eclipse/emf/ecp/emf2web/json/controller/xtend/ViewCleaner.xtend
+++ b/bundles/org.eclipse.emf.ecp.emf2web.json/src/org/eclipse/emf/ecp/emf2web/json/controller/xtend/ViewCleaner.xtend
@@ -36,22 +36,42 @@
 class ViewCleaner {
 	
 	/**
+	 * Number of maximum validation and cleaning iterations on any given view
+	 */
+	static val VALIDATION_TRIES = 5;
+	
+	/**
 	 * Remove invalid and not supported {@link VElement}s.
 	 */
 	def static cleanView(VView view) {
-		removeInvalidElements(view)
-
+		removeInvalidElements(view, VALIDATION_TRIES)
+		
 		val whiteList = #[VLabel, VCategorizationElement, VCategorization, VCategory, VView, VControl, VContainer]
 		removeUnsupportedElements(view, whiteList)
 	}
 
-	private static def void removeInvalidElements(VView view) {
+	/**
+	 * Removing invalid elements from the view might trigger new validation errors.
+	 * Therefore we should try multiple times to validate and clean the view model in case it is invalid.
+	 */
+	private static def void removeInvalidElements(VView view, int tries) {
+		for (var i = 0; i < tries; i++) {
+			var wasInvalid = removeInvalidElements(view);
+			if (!wasInvalid) {
+				// success 
+				return;
+			}
+		}
+	}
+
+	private static def boolean removeInvalidElements(VView view) {
 		val validation = Diagnostician.INSTANCE.validate(view);
 		if (validation.severity == Diagnostic.ERROR) {
 			for (diagnostic : validation.children) {
 				removeInvalidElements(diagnostic)
 			}
 		}
+		validation.severity == Diagnostic.ERROR
 	}
 
 	private static def void removeInvalidElements(Diagnostic diagnostic) {
diff --git a/bundles/org.eclipse.emf.ecp.ide.util/src/org/eclipse/emf/ecp/ide/spi/util/ViewModelHelper.java b/bundles/org.eclipse.emf.ecp.ide.util/src/org/eclipse/emf/ecp/ide/spi/util/ViewModelHelper.java
index 6afb36d..18bd47f 100644
--- a/bundles/org.eclipse.emf.ecp.ide.util/src/org/eclipse/emf/ecp/ide/spi/util/ViewModelHelper.java
+++ b/bundles/org.eclipse.emf.ecp.ide.util/src/org/eclipse/emf/ecp/ide/spi/util/ViewModelHelper.java
@@ -212,6 +212,14 @@
 	 * Helper class for encapsulating view loading functionality.
 	 */
 	public static class ViewLoader {
+		/**
+		 * Loads the view denoted by the given file. Also tries to register referenced Ecores.
+		 *
+		 * @param file the view to load
+		 * @param registeredEcores a collection to which all Ecores which are successfully registered are added.
+		 * @return the {@link VView} denoted by the given file.
+		 * @throws IOException if something goes wrong during loading or registering
+		 */
 		public VView loadView(IFile file, Collection<String> registeredEcores) throws IOException {
 			final String path = getPath(file);
 			final VView view = loadView(path);
@@ -222,10 +230,22 @@
 			return view;
 		}
 
+		/**
+		 * Returns the path string of the given file.
+		 *
+		 * @param file the {@link IFile} for which the path string shall be determined.
+		 * @return The determined string path.
+		 */
 		protected String getPath(IFile file) {
 			return file.getLocation().toString();
 		}
 
+		/**
+		 * Loads the view denoted by the given path.
+		 *
+		 * @param path the path denoting the {@link VView}
+		 * @return the loaded {@link VView}
+		 */
 		protected VView loadView(String path) {
 			final ResourceSet resourceSet = new ResourceSetImpl();
 			final URI fileURI = URI.createFileURI(path);
@@ -236,6 +256,14 @@
 			return null;
 		}
 
+		/**
+		 * Registers the referenced Ecores of the given view.
+		 *
+		 * @param view the {@link VView} which possibly references Ecores.
+		 * @param viewLocation the location of the given view. Used for error reporting.
+		 * @param registeredEcores a collection to which all Ecores which are successfully registered are added.
+		 * @throws IOException if something goes wrong during loading or registering
+		 */
 		protected void registerReferencedEcores(VView view, String viewLocation, Collection<String> registeredEcores)
 			throws IOException {
 			if (view == null || view.getEcorePaths() == null) {
@@ -254,6 +282,13 @@
 			}
 		}
 
+		/**
+		 * Returns a string representation of the view and its location.
+		 *
+		 * @param view the {@link VView}.
+		 * @param viewLocation the location.
+		 * @return a string representation of the view and its location
+		 */
 		protected String getViewNameAndLocation(VView view, String viewLocation) {
 			return getViewName(view)
 				.map(viewName -> MessageFormat.format(Messages.ViewModelHelper_couldNotFindEcorePath_nameAndLocation,
@@ -261,6 +296,12 @@
 				.orElse(viewLocation);
 		}
 
+		/**
+		 * Determines a name for the given view.
+		 *
+		 * @param view the [@link VView}.
+		 * @return an optional possibly containing a determined name, empty otherwise.
+		 */
 		protected Optional<String> getViewName(VView view) {
 			if (view.getLabel() != null && !view.getLabel().isEmpty()) {
 				return Optional.of(view.getLabel());
@@ -274,14 +315,31 @@
 			return Optional.empty();
 		}
 
+		/**
+		 * Indicates whether the Ecore denoted by the path exists in the workspace.
+		 *
+		 * @param ecorePath the potential path to an Ecore
+		 * @return {@code true} if an Ecore exists at the path in the workspace, {@code false} otherwise.
+		 */
 		protected boolean ecoreExistsInWorkspace(String ecorePath) {
 			return ResourcesPlugin.getWorkspace().getRoot().findMember(ecorePath) != null;
 		}
 
+		/**
+		 * Returns the service used for error reporting.
+		 *
+		 * @return the {@link ReportService}
+		 */
 		protected ReportService getReportService() {
 			return Activator.getDefault().getReportService();
 		}
 
+		/**
+		 * Try to register the Ecore denoted by the path.
+		 *
+		 * @param ecorePath the path to the Ecore in the workspace which shall be registered.
+		 * @throws IOException if something goes wrong during registering.
+		 */
 		protected void registerEcore(String ecorePath) throws IOException {
 			EcoreHelper.registerEcore(ecorePath);
 		}