[358545] Formatting a JSP file, containing Java snippets, leads to misformatted or even lost code
diff --git a/tests/org.eclipse.jst.jsp.ui.tests/src/org/eclipse/jst/jsp/ui/tests/format/TestContentFormatter.java b/tests/org.eclipse.jst.jsp.ui.tests/src/org/eclipse/jst/jsp/ui/tests/format/TestContentFormatter.java
index d880b15..173ef6f 100644
--- a/tests/org.eclipse.jst.jsp.ui.tests/src/org/eclipse/jst/jsp/ui/tests/format/TestContentFormatter.java
+++ b/tests/org.eclipse.jst.jsp.ui.tests/src/org/eclipse/jst/jsp/ui/tests/format/TestContentFormatter.java
@@ -11,6 +11,7 @@
 package org.eclipse.jst.jsp.ui.tests.format;
 
 import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 
@@ -22,7 +23,10 @@
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Preferences;
 import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jface.text.DocumentRewriteSession;
+import org.eclipse.jface.text.DocumentRewriteSessionType;
 import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension4;
 import org.eclipse.jface.text.Region;
 import org.eclipse.jface.text.formatter.FormattingContext;
 import org.eclipse.jface.text.formatter.FormattingContextProperties;
@@ -31,19 +35,18 @@
 import org.eclipse.jface.text.source.SourceViewerConfiguration;
 import org.eclipse.jst.jsp.ui.StructuredTextViewerConfigurationJSP;
 import org.eclipse.jst.jsp.ui.tests.util.ProjectUtil;
-import org.eclipse.jst.jsp.ui.tests.util.StringCompareUtil;
 import org.eclipse.wst.html.core.internal.HTMLCorePlugin;
 import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
 import org.eclipse.wst.sse.core.StructuredModelManager;
 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.utils.StringUtils;
 
 public class TestContentFormatter extends TestCase {
 	String wtp_autotest_noninteractive = null;
 	private static final String PROJECT_NAME = "jspformatting";
 	private static final String UTF_8 = "UTF-8";
 
-	private StringCompareUtil fStringCompareUtil;
 	private IFormattingContext fContext;
 
 	protected void setUp() throws Exception {
@@ -62,8 +65,6 @@
 
 		fContext = new FormattingContext();
 		fContext.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT, Boolean.valueOf(true));
-
-		fStringCompareUtil = new StringCompareUtil();
 	}
 
 	private void formatAndAssertEquals(String beforePath, String afterPath, boolean resetPreferences) throws UnsupportedEncodingException, IOException, CoreException {
@@ -95,8 +96,15 @@
 			afterModel.save(afterBytes);
 
 			String expectedContents = new String(afterBytes.toByteArray(), UTF_8);
+			expectedContents = StringUtils.replace(expectedContents, "\r\n", "\n");
+			expectedContents = StringUtils.replace(expectedContents, "\r", "\n");
+
 			String actualContents = new String(formattedBytes.toByteArray(), UTF_8);
-			assertTrue("Formatted document differs from the expected.\nExpected Contents:\n" + expectedContents + "\nActual Contents:\n" + actualContents, fStringCompareUtil.equalsIgnoreLineSeperator(expectedContents, actualContents));
+			actualContents = StringUtils.replace(actualContents, "\r\n", "\n");
+			actualContents = StringUtils.replace(actualContents, "\r", "\n");
+
+			assertEquals("Formatted document differs from the expected.", expectedContents, actualContents);
+			assertTrue(onlyWhiteSpaceDiffers(expectedContents, actualContents));
 		}
 		finally {
 			if (beforeModel != null)
@@ -106,6 +114,45 @@
 		}
 	}
 
+	private void formatAndAssertSignificantEquals(String beforePath, boolean resetPreferences) throws UnsupportedEncodingException, IOException, CoreException {
+		IStructuredModel beforeModel = null;
+		try {
+			beforeModel = getModelForEdit(beforePath);
+			assertNotNull("could not retrieve structured model for : " + beforePath, beforeModel);
+			
+			if (resetPreferences) {
+				resetPreferencesToDefault();
+			}
+
+			SourceViewerConfiguration configuration = new StructuredTextViewerConfigurationJSP();
+			IContentFormatterExtension formatter = (IContentFormatterExtension) configuration.getContentFormatter(null);
+
+			IDocument document = beforeModel.getStructuredDocument();
+			String before = document.get();
+			Region region = new Region(0, document.getLength());
+			fContext.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT, Boolean.TRUE);
+			fContext.setProperty(FormattingContextProperties.CONTEXT_REGION, region);
+			DocumentRewriteSession rewriteSession = null;
+			if (document instanceof IDocumentExtension4) {
+				IDocumentExtension4 extension = (IDocumentExtension4) document;
+				DocumentRewriteSessionType type = DocumentRewriteSessionType.UNRESTRICTED;
+				rewriteSession = (extension.getActiveRewriteSession() != null) ? null : extension.startRewriteSession(type);
+			}
+			formatter.format(document, fContext);
+			String after = document.get();
+			if (document instanceof IDocumentExtension4 && rewriteSession != null) {
+				IDocumentExtension4 extension = (IDocumentExtension4) document;
+				extension.stopRewriteSession(rewriteSession);
+			}
+
+			assertTrue(onlyWhiteSpaceDiffers(before, after));
+		}
+		finally {
+			if (beforeModel != null)
+				beforeModel.releaseFromEdit();
+		}
+	}
+
 	/**
 	 * must release model (from edit) after
 	 * 
@@ -130,6 +177,40 @@
 		return model;
 	}
 
+	/**
+	 * Useful for making sure all significant content was retained.
+	 * 
+	 * @param expectedContents
+	 * @param actualContents
+	 * @return
+	 */
+	private boolean onlyWhiteSpaceDiffers(String expectedContents, String actualContents) {
+		CharArrayWriter writer1 = new CharArrayWriter();
+		char[] expected = expectedContents.toCharArray();
+		for (int i = 0; i < expected.length; i++) {
+			if (!Character.isWhitespace(expected[i]))
+				writer1.write(expected[i]);
+		}
+
+		CharArrayWriter writer2 = new CharArrayWriter();
+		char[] actual = actualContents.toCharArray();
+		for (int i = 0; i < actual.length; i++) {
+			if (!Character.isWhitespace(actual[i]))
+				writer2.write(actual[i]);
+		}
+		writer1.close();
+		writer2.close();
+
+		char[] expectedCompacted = writer1.toCharArray();
+		char[] actualCompacted = writer1.toCharArray();
+		assertEquals("significant character lengths are not the same", expectedCompacted.length, actualCompacted.length);
+		for (int i = 0; i < actualCompacted.length; i++) {
+			assertEquals("significant character differs", expectedCompacted[i], actualCompacted[i]);
+		}
+
+		return true;
+	}
+
 	private void resetPreferencesToDefault() {
 		Preferences preferences = HTMLCorePlugin.getDefault().getPluginPreferences();
 		preferences.setToDefault(HTMLCorePreferenceNames.SPLIT_MULTI_ATTRS);
@@ -168,4 +249,8 @@
 		String afterPath = "/" + PROJECT_NAME + "/WebContent/formatbug102495_4-fmt.jsp";
 		formatAndAssertEquals(beforePath, afterPath, true);
 	}
+
+	public void testFormatBug358545() throws UnsupportedEncodingException, IOException, CoreException {
+		formatAndAssertSignificantEquals("/" + PROJECT_NAME + "/WebContent/formatbug358545.jsp", true);
+	}
 }
diff --git a/tests/org.eclipse.jst.jsp.ui.tests/testfiles/jspformatting/WebContent/formatbug358545.jsp b/tests/org.eclipse.jst.jsp.ui.tests/testfiles/jspformatting/WebContent/formatbug358545.jsp
new file mode 100644
index 0000000..622877c
--- /dev/null
+++ b/tests/org.eclipse.jst.jsp.ui.tests/testfiles/jspformatting/WebContent/formatbug358545.jsp
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

+<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" 	import="com.eprice.data.*,

+         com.eprice.data.PricingPlanAttribute.AttributeSourceEnum,

+         com.eprice.data.Currency,

+         com.epriceadmin.form.*,

+         java.util.Iterator,      

+         com.epriceadmin.service.PriceBookService,

+         com.epriceadmin.data.*,

+         com.eprice.data.pricebook.*,

+         com.eprice.importsexports.data.AttributeValue,

+         java.util.*,

+	java.text.*"

+%>

+<html>

+<head>

+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>

+<%@ taglib prefix="eprice" uri="WEB-INF/tld/eprice.tld"%>

+<jsp:useBean id="AdminSession" scope="session" type="com.epriceadmin.data.AdminSession" />

+<c:set var="activeForm" value="${AdminSession.activeForm}" scope="session" />

+<script type="text/javascript">

+	var oldValue = "";

+

+	function copyPrices(prevValue) {

+		var pricetxts = document.forms[0].priceField;

+		if (isNaN(pricetxts[0].value)) {

+			alert('Cannot copy non-numeric prices');

+			return;

+		}

+		for ( var i = 0; i < pricetxts.length; i++) {

+			if (!isNaN(pricetxts[0].value)) {

+				pricetxts[i].value = pricetxts[0].value

+				oldValue = prevValue

+			}

+		}

+

+	}

+

+	

+	function controlForm() {

+<%					if( priceBooksEditIndividualForm.getShowAttributes() != null ) {			%>

+	document.getElementById("savePrices").className = "buttonDisabled";

+		document.getElementById("savePrices").mouseover.className = "buttonDisabled";

+<%		}			%>

+	}

+

+	function MM_findObj(n, d) { //v4.01

+		var p, i, x;

+		if (!d)

+			d = document;

+		if ((p = n.indexOf("?")) > 0 && parent.frames.length) {

+			d = parent.frames[n.substring(p + 1)].document;

+			n = n.substring(0, p);

+		}

+		if (!(x = d[n]) && d.all)

+			x = d.all[n];

+		for (i = 0; !x && i < d.forms.length; i++)

+			x = d.forms[i][n];

+		for (i = 0; !x && d.layers && i < d.layers.length; i++)

+			x = MM_findObj(n, d.layers[i].document);

+		if (!x && d.getElementById)

+			x = d.getElementById(n);

+		return x;

+	}

+

+	function MM_showHideLayers() { //v6.0

+		var i, p, v, obj, args = MM_showHideLayers.arguments;

+		for (i = 0; i < (args.length - 2); i += 3)

+			if ((obj = MM_findObj(args[i])) != null) {

+				v = args[i + 2];

+				if (obj.style) {

+					obj = obj.style;

+					v = (v == 'show') ? 'visible' : (v == 'hide') ? 'hidden'

+							: v;

+				}

+				obj.visibility = v;

+			}

+	}

+	function verifyLocalAcessPLI() {

+<% if( request.getAttribute( "LOCAL_DIVERSE_ACESS_PLI" ) != null ) {%>

+	MM_showHideLayers('restrictLocalAccessPLIPopup', '', 'show');

+<% }%>

+	}

+</script>

+<title>ePrice Administration - Price Books - Edit Individual Prices</title>

+</head>

+<body onkeydown="selectSave()" onload="verifyLocalAcessPLI();">

+	

+

+	

+</body>

+</html>