/** * Copyright (c) 2007-2012 IBM Corporation 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: * IBM - Initial API and implementation */ package org.eclipse.emf.ecore.resource.impl; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.content.IContentDescriber; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.content.ITextContentDescriber; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.ContentHandler; import org.eclipse.emf.ecore.resource.URIConverter; /** * An implementation of a content handler. */ public class ContentHandlerImpl implements ContentHandler { /** * Creates a map with a single entry from {@link ContentHandler#VALIDITY_PROPERTY} to the given validity value. * @param validity the value of the validity property. * @return a map with a single entry from {@link ContentHandler#VALIDITY_PROPERTY} to the given validity value. */ public static Map<String, Object> createContentDescription(Validity validity) { Map<String, Object> result = new HashMap<String, Object>(); result.put(VALIDITY_PROPERTY, validity); return result; } /** * Creates an instance. */ public ContentHandlerImpl() { super(); } /** * Returns the value of {@link ContentHandler#OPTION_REQUESTED_PROPERTIES} in the options map. * @param options the options in which to look up the property. * @return value of {@link ContentHandler#OPTION_REQUESTED_PROPERTIES} in the options map. */ @SuppressWarnings("unchecked") protected Set<String> getRequestedProperties(Map<?, ?> options) { return (Set<String>)options.get(OPTION_REQUESTED_PROPERTIES); } /** * Returns whether the named property is one requested in the options. * @param property the property in question. * @param options the options in which to look for the requested property. * @return whether the named property is one requested in the options. * @see #getRequestedProperties(Map) */ protected boolean isRequestedProperty(String property, Map<?, ?> options) { if (ContentHandler.VALIDITY_PROPERTY.equals(property) || ContentHandler.CONTENT_TYPE_PROPERTY.equals(property)) { return true; } else { Set<String> requestedProperties = getRequestedProperties(options); if (requestedProperties == null) { return true; } else { return requestedProperties.contains(property); } } } /** * This implementations always return true; clients are generally expected to override this. * @param uri the URI in questions. * @return true; */ public boolean canHandle(URI uri) { return true; } /** * This base implementation handles computing the {@link ContentHandler#BYTE_ORDER_MARK_PROPERTY}, * the {@link ContentHandler#CHARSET_PROPERTY character set property}, * and the {@link ContentHandler#LINE_DELIMITER_PROPERTY line delimiter property}. * for each such {@link #isRequestedProperty(String, Map) requested property}. */ public Map<String, Object> contentDescription(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException { Map<String, Object> result = createContentDescription(ContentHandler.Validity.INDETERMINATE); if (isRequestedProperty(ContentHandler.BYTE_ORDER_MARK_PROPERTY, options)) { ByteOrderMark byteOrderMark = getByteOrderMark(uri, inputStream, options, context); if (byteOrderMark != null) { result.put(ContentHandler.BYTE_ORDER_MARK_PROPERTY, byteOrderMark); } } if (isRequestedProperty(ContentHandler.CHARSET_PROPERTY, options)) { String charset = getCharset(uri, inputStream, options, context); if (charset != null) { result.put(ContentHandler.CHARSET_PROPERTY, charset); } } if (isRequestedProperty(ContentHandler.LINE_DELIMITER_PROPERTY, options)) { String lineDelimiter = getLineDelimiter(uri, inputStream, options, context); if (lineDelimiter != null) { result.put(ContentHandler.LINE_DELIMITER_PROPERTY, lineDelimiter); } } return result; } /** * Returns the character set of the input stream; this implementation simply returns null. * @param uri the URI of the input stream. * @param inputStream the input stream. * @param options any options that might influence the interpretation of the content. * @param context a cache for previously computed information. * @return the character set of the input stream. * @throws IOException if there is a problem loading the content. * @since 2.9 */ protected String getCharset(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException { return null; } /** * Returns the line delimiter of the input stream; it's computed from the bytes interpreted using the {@link #getCharset(URI, InputStream, Map, Map) appropriate character set}. * @param uri the URI of the input stream. * @param inputStream the input stream. * @param options any options that might influence the interpretation of the content. * @param context a cache for previously computed information. * @return the line delimiter of the input stream. * @throws IOException if there is a problem loading the content. * @since 2.9 */ protected String getLineDelimiter(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException { String result = (String)context.get(ContentHandler.LINE_DELIMITER_PROPERTY); if (result == null) { String charset = getCharset(uri, inputStream, options, context); if (charset != null) { result = getLineDelimiter(inputStream, charset); if (result != null) { context.put(ContentHandler.LINE_DELIMITER_PROPERTY, result); } } } return result; } /** * * Returns the line delimiter of the input stream interpreted using the specified character set. * @since 2.9 */ public static String getLineDelimiter(InputStream inputStream, String charset) throws IOException { Reader reader = new InputStreamReader(inputStream, charset); char [] text = new char [4048]; char target = 0; for (int count = reader.read(text); count > -1; count = reader.read(text)) { for (int i = 0; i < count; ++i) { char character = text[i]; if (character == '\n') { if (target == '\n') { return "\n"; } else if (target == '\r') { return "\r\n"; } else { target = '\n'; } } else if (character == '\r') { if (target == '\n') { return "\n\r"; } else if (target == '\r') { return "\r"; } else { target = '\r'; } } } } return null; } /** * Returns the byte order marker at the start of the input stream. * @param uri the URI of the input stream. * @param inputStream the input stream to scan. * @param options any options to influence the behavior; this base implementation ignores this. * @param context the cache for fetching and storing a previous computation of the byte order marker; this base implementation caches {@link ContentHandler#BYTE_ORDER_MARK_PROPERTY}. * @return the byte order marker at the start of the input stream. * @throws IOException */ protected ByteOrderMark getByteOrderMark(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException { ByteOrderMark result = (ByteOrderMark)context.get(ContentHandler.BYTE_ORDER_MARK_PROPERTY); if (result == null) { result = ByteOrderMark.read(inputStream); inputStream.reset(); context.put(ContentHandler.BYTE_ORDER_MARK_PROPERTY, result); } return result; } /** * An implementation of a describer that delegates to a {@link ContentHandler}. */ public static class Describer implements IContentDescriber, ITextContentDescriber, IExecutableExtension { /** * An property that, in addition to {@link IContentDescription#CHARSET} and {@link IContentDescription#BYTE_ORDER_MARK}, is supported by all EMF's content describers. * @since 2.9 */ public static final QualifiedName LINE_DELIMITER = new QualifiedName(LINE_DELIMITER_PROPERTY.substring(0, LINE_DELIMITER_PROPERTY.indexOf(':')), LINE_DELIMITER_PROPERTY.substring(LINE_DELIMITER_PROPERTY.indexOf(':') + 1)) ; /** * The content handler delegate. */ protected ContentHandler contentHandler; /** * Returns the qualified names of the supported options. * This base implementation supports only {@link #LINE_DELIMITER}, {@link IContentDescription#CHARSET}, and {@link IContentDescription#BYTE_ORDER_MARK}. * @return the qualified names of the supported options. */ public QualifiedName[] getSupportedOptions() { return SUPPORTED_OPTIONS; } /** * This base implementation supports only {@link #LINE_DELIMITER}, {@link IContentDescription#CHARSET}, and {@link IContentDescription#BYTE_ORDER_MARK}. */ private static final QualifiedName [] SUPPORTED_OPTIONS = { IContentDescription.CHARSET, IContentDescription.BYTE_ORDER_MARK, LINE_DELIMITER }; /** * Returns the qualified name converted to the corresponding property string. * @param qualifiedName the qualified name to convert. * @return the qualified name converted to the corresponding property string. */ protected String getProperty(QualifiedName qualifiedName) { return qualifiedName.toString(); } /** * Returns the given property's basic EMF value converted to the corresponding Eclipse value. * @param qualifiedName the name of the property for which this value applies. * @param value the value to convert. * @return the given property's basic EMF value converted to the corresponding Eclipse value. */ protected Object getDescriptionValue(QualifiedName qualifiedName, Object value) { if (value == null) { return null; } else if (IContentDescription.BYTE_ORDER_MARK.equals(qualifiedName)) { return ((ContentHandler.ByteOrderMark)value).bytes(); } else { return value; } } public int describe(InputStream inputStream, IContentDescription description) throws IOException { Map<Object, Object> options = new HashMap<Object, Object>(); Map<String, ?> result; if (description != null) { Map<String, QualifiedName> requestedPropertyToQualifiedNameMap = new HashMap<String, QualifiedName>(); Set<String> requestedProperties = new HashSet<String>(); for (QualifiedName qualifiedName : getSupportedOptions()) { if (description.isRequested(qualifiedName)) { String property = getProperty(qualifiedName); if (property != null) { requestedPropertyToQualifiedNameMap.put(property, qualifiedName); requestedProperties.add(property); } } } options.put(ContentHandler.OPTION_REQUESTED_PROPERTIES, requestedProperties); result = contentHandler.contentDescription(URI.createURI("*"), inputStream, options, new HashMap<Object, Object>()); for (Map.Entry<String, ?> property : result.entrySet()) { QualifiedName qualifiedName = requestedPropertyToQualifiedNameMap.get(property.getKey()); if (qualifiedName != null) { Object value = getDescriptionValue(qualifiedName, property.getValue()); if (value != null) { description.setProperty(qualifiedName, value); } } } } else { options.put(ContentHandler.OPTION_REQUESTED_PROPERTIES, Collections.emptySet()); result = contentHandler.contentDescription(URI.createURI("*"), inputStream, options, new HashMap<Object, Object>()); } return ((ContentHandler.Validity)result.get(ContentHandler.VALIDITY_PROPERTY)).ordinal(); } public int describe(Reader reader, IContentDescription description) throws IOException { return describe(new URIConverter.ReadableInputStream(reader), description); } public void setInitializationData(IConfigurationElement configurationElement, String propertyName, Object data) throws CoreException { Map<String, String> parameters = getParameters(configurationElement, propertyName, data); contentHandler = createContentHandler(parameters); } /** * Returns the new content handler for the given parameters that were supplied by the registration of the Eclipse content type. * @param parameters the parameter for configuring the content handler. * @return the next content handler. */ protected ContentHandler createContentHandler(Map<String, String> parameters) { return null; } /** * The key in the {@link #getParameters(IConfigurationElement, String, Object) parameters map} representing the content type identifier String. */ protected static final String CONTENT_TYPE_ID = "contentTypeID"; /** * The key in the {@link #getParameters(IConfigurationElement, String, Object) parameters map} representing the extensions, * which are encoded as a space separate list of suffixes. */ protected static final String EXTENSIONS = "extensions"; /** * Returns the map of parameters as fetched from the given configuration element's information. * This implementation populates the {@link #CONTENT_TYPE_ID content type identifier} and the {@link #EXTENSIONS extensions}. * @param configurationElement the configuration element of the content type. * @param propertyName the property for this particular for this instance. * @param data the data associated with this instance. * @return the map of parameters as fetched from the given configuration element's information. */ protected Map<String, String> getParameters(IConfigurationElement configurationElement, String propertyName, Object data) { Map<String, String> parameters = new HashMap<String, String>(); if (data != null) { @SuppressWarnings("unchecked") Map<String,String> dataMap = (Map<String,String>)data; parameters.putAll(dataMap); parameters.put(CONTENT_TYPE_ID, configurationElement.getAttribute("id")); String fileExtensions = configurationElement.getAttribute("file-extensions"); if (fileExtensions != null) { parameters.put(EXTENSIONS, fileExtensions.replace(',', ' ')); } } return parameters; } } }