/** * Copyright (c) 2012 Ed Merks and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Ed Merks - Initial API and implementation */ package org.eclipse.emf.test.core.ecore; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.CharArrayWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.ContentHandler; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.URIConverter; import org.eclipse.emf.ecore.resource.impl.ContentHandlerImpl; import org.eclipse.emf.ecore.resource.impl.PlatformContentHandlerImpl; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.ecore.xmi.impl.XMLContentHandlerImpl; import org.eclipse.emf.test.models.tree.Data; import org.eclipse.emf.test.models.tree.Node; import org.eclipse.emf.test.models.tree.TreeFactory; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; /** * Test that {@link URIConverter#contentDescription(URI, Map) content descriptions} function properly * and that {@link Resource#OPTION_LINE_DELIMITER} is properly supported based on that. */ public class ContentTypeTest extends TestCase { protected IProject project; protected URI projectURI = URI.createURI("platform:/resource/testProject"); protected ResourceSet resourceSet; protected IWorkspaceRoot root; public ContentTypeTest(String name) { super(name); } public static Test suite() { TestSuite ts = new TestSuite("ContentTypeTest"); ts.addTest(new ContentTypeTest("testAllCombinations")); return ts; } /** * Sets up a resource set and an empty project. */ @Override protected void setUp() throws Exception { root = ResourcesPlugin.getWorkspace().getRoot(); project = root.getProject(projectURI.segment(1)); project.create(null); project.open(null); resourceSet = new ResourceSetImpl(); } /** * Deleted the test project. */ @Override protected void tearDown() throws Exception { project.delete(IProject.FORCE, null); } /** * XML encodings to test. */ protected static final String [] ENCODINGS = new String[] { "ASCII", "UTF-8", "UTF-16" }; /** * Line separator preferences to test. */ protected static final String LINUX = "\n"; protected static final String WINDOWS = "\r\n"; protected static final String MAC_OS = "\r"; protected static final String [] LINE_SEPARATOR_PREFERENCES = new String[] { LINUX, WINDOWS, MAC_OS }; /** * Simulates setting a project level workspace preference for the desired line endings for newly created files. */ protected void setLineSeparatorPreference(String lineSeparator) throws Exception { IEclipsePreferences node = new ProjectScope(project).getNode(Platform.PI_RUNTIME); node.put(Platform.PREF_LINE_SEPARATOR, lineSeparator); node.flush(); } /** * Test all the combinations of encodings and line separators preferences across EMF's platform integrated content handler as well as EMF's standalone content handler. */ public void testAllCombinations() throws Exception { // Create a new resource in the resource set. // URI uri = projectURI.appendSegment("tree.xmi"); Resource resource = resourceSet.createResource(uri, "org.eclipse.emf.ecore.xmi"); EList<ContentHandler> contentHandlers = resourceSet.getURIConverter().getContentHandlers(); // Create a simple model that includes data with line feeds. // We'd expect these to be escaped and have no impact on the determination of the resource's line delimiter. // Node node = TreeFactory.eINSTANCE.createNode(); Data data = TreeFactory.eINSTANCE.createData(); data.setName("\n\r\r\n\n\n"); node.setData(data); resource.getContents().add(node); resource.getContents().add(data); // Iterate over the various line separator preferences. // int counter = 0; for (final String lineSeparator : LINE_SEPARATOR_PREFERENCES) { // Simulate the workspace preference for the line separator. // setLineSeparatorPreference(lineSeparator); // Simulate a stand alone version of the content handler that returns the preferred line delimiter for files new files. // XMLContentHandlerImpl.XMI xmiContentHandler = new XMLContentHandlerImpl.XMI() { @Override protected String getLineDelimiter(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException { String result = super.getLineDelimiter(uri, inputStream, options, context); return result == null ? lineSeparator : result; } }; // Try everything with both the standard registered content handler and the specialized XMI content handler. // for (ContentHandler contentHandler : new ContentHandler[] { new PlatformContentHandlerImpl(), xmiContentHandler}) { contentHandlers.clear(); contentHandlers.add(contentHandler); // Try everything for the various character encodings. // for (String encoding : ENCODINGS) { // Use a unique new URI in an attempt to avoid Linux problems with the platform caching the old information. // resource.setURI(resource.getURI().trimSegments(1).appendSegment("tree_" + ++counter + ".xmi")); String message = "Combination: " + lineSeparator.replace("\n", "\\n").replace("\r", "\\r") + " " + encoding + (contentHandler == xmiContentHandler ? " stand-alone" : " platform-integrated"); // Specify the encoding to be used for saving as well as the option to determine the desired line delimiter during save. // Map<String, String> options = new HashMap<String, String>(); options.put(XMLResource.OPTION_ENCODING, encoding); options.put(Resource.OPTION_LINE_DELIMITER, Resource.OPTION_LINE_DELIMITER_UNSPECIFIED); resource.save(options); // Check that this really produces a workspace file. // IFile file = root.getFile(new Path(resource.getURI().toPlatformString(true))); assertTrue(message, file.exists()); // Check that the content description is as expected. // validateContentDescription(message, file, encoding, lineSeparator); // Test that changing the file contents to use the updated separator and then saving the resource, preserves the separator currently in the contents. // for (String updatedLineSeparator : LINE_SEPARATOR_PREFERENCES) { // Update the contents of the file to use this line separator, and check that it's been properly updated. // replace(file, updatedLineSeparator); validateContentDescription(message, file, encoding, updatedLineSeparator); // Save the resource and check that it's update the separator in the existing contents. // resource.save(options); validateContentDescription(message, file, encoding, updatedLineSeparator); } // Test that specifying the encoding and the specific desired line separator in the options for save produce results with exactly that encoding and line separator. // for (String updatedLineSeparator : LINE_SEPARATOR_PREFERENCES) { for (String updatedEncoding : ENCODINGS) { options.put(XMLResource.OPTION_ENCODING, updatedEncoding); options.put(Resource.OPTION_LINE_DELIMITER, updatedLineSeparator); resource.save(options); validateContentDescription(message, file, updatedEncoding, updatedLineSeparator); } } // Delete the file before the next iteration of the loop. // file.delete(true, null); assertTrue(message, !file.exists()); } } } } private static final Pattern LINE_DELIMITER_PATTERN = Pattern.compile("(\n\r?)|(\r\n?)"); /** * Replaces the contents of the file to use the given line separator. */ protected void replace(IFile file, String lineSeparator) throws IOException, CoreException { InputStream contents = file.getContents(); String charset = file.getCharset(); BufferedReader reader = new BufferedReader(new InputStreamReader(contents, charset)); CharArrayWriter writer = new CharArrayWriter(); int c; while ((c = reader.read()) != -1) { writer.write(c); } contents.close(); String string = writer.toString(); String newContents = LINE_DELIMITER_PATTERN.matcher(string).replaceAll(lineSeparator); byte[] bytes = newContents.getBytes(charset); file.setContents(new ByteArrayInputStream(bytes), IResource.FORCE, null); } protected void validateContentDescription(String message, IFile file, String encoding, String lineSeparator) throws IOException, CoreException { // Check EMF's content description support for both platform resource access and for direct access to the underlying file system. // for (URI accessURI : new URI [] { URI.createPlatformResourceURI(file.getFullPath().toString(), true), URI.createFileURI(file.getLocation().toString())}) { // Check that the content type can be determine and that all the properties have the expected values. // Map<String, ?> emfContentDescription = resourceSet.getURIConverter().contentDescription(accessURI, null); assertEquals(message, ContentHandler.Validity.VALID, emfContentDescription.get(ContentHandler.VALIDITY_PROPERTY)); assertEquals(message, "org.eclipse.emf.ecore.xmi", emfContentDescription.get(ContentHandler.CONTENT_TYPE_PROPERTY)); assertEquals(message, encoding, emfContentDescription.get(ContentHandler.CHARSET_PROPERTY)); assertEquals(message, lineSeparator, emfContentDescription.get(ContentHandler.LINE_DELIMITER_PROPERTY)); Object byteOrderMark = emfContentDescription.get(ContentHandler.BYTE_ORDER_MARK_PROPERTY); if ("UTF-16".equals(encoding)) { // We only expect byte order markers for UTF-16 encoding. // The endian is hardware dependent, so we tolerate either one so the test passed for all hardware. // assertTrue(message, ContentHandler.ByteOrderMark.UTF_16BE == byteOrderMark || ContentHandler.ByteOrderMark.UTF_16LE == byteOrderMark); } else { assertEquals(message, null, byteOrderMark); } } // Check that the integration with the platform's content description mechanism produces the same expected results. // IContentDescription contentDescription = file.getContentDescription(); assertEquals(message, "org.eclipse.emf.ecore.xmi", contentDescription.getContentType().getId()); assertEquals(message, encoding, contentDescription.getProperty(IContentDescription.CHARSET)); assertEquals(message, lineSeparator, contentDescription.getProperty(ContentHandlerImpl.Describer.LINE_DELIMITER)); Object byteOrderMark = contentDescription.getProperty(IContentDescription.BYTE_ORDER_MARK); if ("UTF-16".equals(encoding)) { // We only expect byte order markers for UTF-16 encoding. // The endian is hardware dependent, so we tolerate either one so the test passed for all hardware. // assertTrue(message, IContentDescription.BOM_UTF_16BE == byteOrderMark || IContentDescription.BOM_UTF_16LE == byteOrderMark); } else { assertEquals(message, null, byteOrderMark); } } }