/** * Copyright (c) 2002-2007 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; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl; /** * A converter to normalize a URI or to produce an input or output stream for a URI. * <p> * A resource set provides {@link ResourceSet#getURIConverter one} of these * for use by it's {@link ResourceSet#getResources resources} * when they are {@link Resource#save(java.util.Map) serialized} and {@link Resource#load(java.util.Map) deserialized}. * A resource set also uses this directly when it {@link ResourceSet#getResource looks up} a resource: * a resource is considered a match if {@link Resource#getURI it's URI}, * and the URI being looked up, * {@link #normalize normalize} to {@link URI#equals(Object) equal} URIs. * Clients must extend the default {@link org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl implementation}, * since methods can and will be added to this API. * </p> * @see ResourceSet#getURIConverter() * @see URIHandler * @see ContentHandler */ public interface URIConverter { /** * An option used to pass the calling URIConverter to the {@link URIHandler}s. * @since 2.4 */ String OPTION_URI_CONVERTER = "URI_CONVERTER"; /** * An option to pass a {@link Map Map<Object, Object>} to any of the URI converter's methods * in order to yield results in addition to the returned value of the method. * @since 2.4 */ String OPTION_RESPONSE = "RESPONSE"; /** * A property of the {@link #OPTION_RESPONSE response option} * used to yield the {@link #ATTRIBUTE_TIME_STAMP time stamp} associated * with the creation of an {@link #createInputStream(URI, Map) input} or an {@link #createOutputStream(URI, Map) output} stream. * This is typically used by resource {@link Resource#load(Map) load} and {@link Resource#save(Map) save} * in order to set the {@link Resource#getTimeStamp()}. * @since 2.4 */ String RESPONSE_TIME_STAMP_PROPERTY = "TIME_STAMP"; /** * A property of the {@link #OPTION_RESPONSE response option} * used to yield the newly allocated URI associated * with the creation of an {@link #createOutputStream(URI, Map) output} stream. * This is typically used by resource {@link Resource#save(Map) save} * in order to {@link Resource#setURI(URI) set the resource URI}. * @since 2.7 */ String RESPONSE_URI = "URI"; /** * Returns the normalized form of the URI. * <p> * This may, in theory, do absolutely anything. * Default behaviour includes * applying URI {@link URIConverter#getURIMap mapping}, * assuming <code>"file:"</code> protocol * for a {@link URI#isRelative relative} URI with a {@link URI#hasRelativePath relative path}: *<pre> * ./WhateverDirectory/Whatever.file * -> * file:./WhateverDirectory/Whatever.file *</pre> * and assuming <code>"platform:/resource"</code> protocol * for a relative URI with an {@link URI#hasAbsolutePath absolute path}: *<pre> * /WhateverRelocatableProject/Whatever.file * -> * platform:/resource/WhateverRelocatableProject/Whatever.file *</pre> * </p> * <p> * It is important to emphasize that normalization can result in loss of information. * The normalized URI should generally be used only for comparison and for access to input or output streams. * </p> * @param uri the URI to normalize. * @return the normalized form. * @see org.eclipse.emf.ecore.plugin.EcorePlugin#getPlatformResourceMap */ URI normalize(URI uri); /** * Returns the map used for remapping a logical URI to a physical URI when {@link #normalize normalizing}. * <p> * An implementation will typically also delegate to the {@link URIConverter#URI_MAP global} map, * so registrations made in this map are <em>local</em> to this URI converter, * i.e., they augment or override those of the global map. * </p> * <p> * The map generally specifies instance to instance mapping, * except for the case that both the key URI and the value URI end with "/", * which specifies a folder to folder mapping. * A folder mapping will remap any URI that has the key as its {@link URI#replacePrefix prefix}, * e.g., if the map contains: *<pre> * http://www.example.com/ -> platform:/resource/example/ *</pre> * then the URI *<pre> * http://www.example.com/a/b/c.d *</pre> * will map to *<pre> * platform:/resource/example/a/b/c.d *</pre> * A matching instance mapping is considered first. * If there isn't one, the folder mappings are considered starting with the {@link URI#segmentCount() longest} prefix. * </p> * @see #normalize(URI) * @see #URI_MAP * @return the map used for remapping a logical URI to a physical URI. */ Map<URI, URI> getURIMap(); /** * The global static URI map. * Registrations made in this instance will (typically) be available * for {@link URIConverter#normalize use} by any URI converter. * It is populated by URI mappings registered via * {@link org.eclipse.emf.ecore.plugin.EcorePlugin.Implementation#startup() plugin registration}. * @see #normalize(URI) */ Map<URI, URI> URI_MAP = org.eclipse.emf.ecore.resource.impl.URIMappingRegistryImpl.INSTANCE.map(); /** * Returns the list of {@link URIHandler}s. * @return the list of {@link URIHandler}s. * @since 2.4 */ EList<URIHandler> getURIHandlers(); /** * Returns the first URI handler in the {@link #getURIHandler(URI) list} of URI handlers which {@link URIHandler#canHandle(URI) can handle} the given URI. * @param uri the URI for which to find a handler. * @return the first URI handler in the list of URI handlers which can handle the given URI. * @throws RuntimeException if no matching handler is found. * @since 2.4 */ URIHandler getURIHandler(URI uri); /** * Returns the list of {@link ContentHandler}s. * @return the list of {@link ContentHandler}s. * @since 2.4 */ EList<ContentHandler> getContentHandlers(); /** * Creates an input stream for the URI and returns it; * it has the same effect as calling {@link #createInputStream(URI, Map) createInputStream(uri, null)}. * @param uri the URI for which to create the input stream. * @return an open input stream. * @exception IOException if there is a problem obtaining an open input stream. * @see #createInputStream(URI, Map) */ InputStream createInputStream(URI uri) throws IOException; /** * Creates an input stream for the URI and returns it. * <p> * It {@link #normalize normalizes} the URI and uses that as the basis for further processing. * Special requirements, such as an Eclipse file refresh, * are handled by the {@link org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl default implementation}. * </p> * @param uri the URI for which to create the input stream. * @param options a map of options to influence the kind of stream that is returned; unrecognized options are ignored and <code>null</code> is permitted. * @return an open input stream. * @exception IOException if there is a problem obtaining an open input stream. * @since 2.4 */ InputStream createInputStream(URI uri, Map<?, ?> options) throws IOException; /** * An interface that is optionally implemented by the input streams returned from * {@link URIConverter#createInputStream(URI)} and {@link URIConverter#createInputStream(URI, Map)}. * An input stream implementing this interface is highly unlikely to support {@link InputStream#read() read}. * Instead {@link #loadResource(Resource) loadResource} should be called. * @since 2.7 */ interface Loadable { /** * Load the contents of the resource directly from the backing store for which the stream implementing this interface is a facade. * @param resource the resource to load. * @throws IOException if there are any problems load the resource from the backing store. */ void loadResource(Resource resource) throws IOException; } /** * An interface that is optionally implemented by the input streams returned from * {@link URIConverter#createInputStream(URI)} and {@link URIConverter#createInputStream(URI, Map)}. * @see ReadableInputStream */ interface Readable { /** * Returns a reader that provides access to the same underlying data as the input stream itself. * @return a reader that provides access to the same underlying data as the input stream itself. */ Reader asReader(); /** * Returns the encoding used to convert the reader's characters to bytes. * @return the encoding used to convert the reader's characters to bytes. */ String getEncoding(); } /** * A wrapper around a reader that implements an input stream but can be unwrapped to access the reader directly. */ class ReadableInputStream extends InputStream implements Readable { private static final Pattern XML_HEADER = Pattern.compile("<\\?xml\\s+(?:version\\s*=\\s*\"[^\"]*\"\\s+)encoding\\s*=\\s*\"\\s*([^\\s\"]*)\"\\s*\\?>"); public static String getEncoding(String xmlString) { Matcher matcher = XML_HEADER.matcher(xmlString); return matcher.lookingAt() ? matcher.group(1) : null; } /** * @since 2.4 */ public static String getEncoding(Reader xmlReader) { try { xmlReader.mark(100); char [] buffer = new char[100]; int length = xmlReader.read(buffer); if (length > -1) { Matcher matcher = XML_HEADER.matcher(new String(buffer, 0, length)); return matcher.lookingAt() ? matcher.group(1) : null; } else { return null; } } catch (IOException exception) { return null; } finally { try { xmlReader.reset(); } catch (IOException exception) { // Ignore. } } } protected String encoding; protected Reader reader; protected Buffer buffer; public ReadableInputStream(Reader reader, String encoding) { super(); this.reader = reader; this.encoding = encoding; } /** * @since 2.4 */ public ReadableInputStream(Reader xmlReader) { super(); this.reader = xmlReader.markSupported() ? xmlReader : new BufferedReader(xmlReader); this.encoding = getEncoding(this.reader); } public ReadableInputStream(String string, String encoding) { this(new StringReader(string), encoding); } public ReadableInputStream(String xmlString) { this(new StringReader(xmlString), getEncoding(xmlString)); } @Override public int read() throws IOException { if (buffer == null) { buffer = new Buffer(100); } return buffer.read(); } public Reader asReader() { return reader; } public String getEncoding() { return encoding; } @Override public void close() throws IOException { super.close(); reader.close(); } @Override public synchronized void reset() throws IOException { super.reset(); reader.reset(); } protected class Buffer extends ByteArrayOutputStream { protected int index; protected char [] characters; protected OutputStreamWriter writer; public Buffer(int size) throws IOException { super(size); characters = new char [size]; writer = new OutputStreamWriter(this, encoding); } public int read() throws IOException { if (index < count) { return buf[index++]; } else { index = 0; reset(); int readCount = reader.read(characters); if (readCount < 0) { return -1; } else { writer.write(characters, 0, readCount); writer.flush(); return buf[index++]; } } } } } /** * Creates an output stream for the URI and returns it; * it has the same effect as calling {@link #createOutputStream(URI, Map) createOutputStream(uri, null)}. * @return an open output stream. * @exception IOException if there is a problem obtaining an open output stream. * @see #createOutputStream(URI, Map) */ OutputStream createOutputStream(URI uri) throws IOException; /** * Creates an output stream for the URI and returns it. * <p> * It {@link #normalize normalizes} the URI and uses that as the basis for further processing. * Special requirements, such as an Eclipse file refresh, * are handled by the {@link org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl default implementation}. * </p> * @param uri the URI for which to create the output stream. * @param options a map of options to influence the kind of stream that is returned; unrecognized options are ignored and <code>null</code> is permitted. * @return an open output stream. * @exception IOException if there is a problem obtaining an open output stream. * @since 2.4 */ OutputStream createOutputStream(URI uri, Map<?, ?> options) throws IOException; /** * An interface that is optionally implemented by the output streams returned from * {@link URIConverter#createOutputStream(URI)} and {@link URIConverter#createOutputStream(URI, Map)}. * An output stream implementing this interface is highly unlikely to support {@link OutputStream#write(int) write}. * Instead {@link #saveResource(Resource) saveResource} should be called. * @since 2.7 */ interface Saveable { /** * Save the contents of the resource directly to the backing store for which the stream implementing this interface is a facade. * @param resource the resource to save. * @throws IOException if there are any problems saving the resource to the backing store. */ void saveResource(Resource resource) throws IOException; } /** * An interface that is optionally implemented by the output streams returned from * {@link URIConverter#createOutputStream(URI)} and {@link URIConverter#createOutputStream(URI, Map)}. * @see WriteableOutputStream */ interface Writeable { /** * Returns a writer that provides access to the same underlying data as the input stream itself. * @return a writer that provides access to the same underlying data as the input stream itself. */ Writer asWriter(); /** * Returns the encoding used to convert the writer's bytes to characters. * @return the encoding used to convert the writer's bytes to characters. */ String getEncoding(); } /** * A wrapper around a writer that implements an output stream but can be unwrapped to access the writer directly. */ static class WriteableOutputStream extends OutputStream implements Writeable { protected String encoding; protected Writer writer; protected Buffer buffer; public WriteableOutputStream(Writer writer, String encoding) { super(); this.writer = writer; this.encoding = encoding; } @Override public void write(int b) throws IOException { if (buffer == null) { buffer = new Buffer(100); } buffer.write(b); } public Writer asWriter() { return writer; } public String getEncoding() { return encoding; } @Override public void close() throws IOException { super.close(); writer.close(); } @Override public void flush() throws IOException { super.flush(); buffer.flush(); writer.flush(); } protected class Buffer extends ByteArrayInputStream { protected int index; protected char [] characters; protected InputStreamReader reader; public Buffer(int size) throws IOException { super(new byte [size], 0, 0); characters = new char [size]; reader = new InputStreamReader(this, encoding); } public void write(int b) throws IOException { if (count < buf.length) { buf[count++] = (byte)b; } else { int readCount = reader.read(characters); if (readCount > 0) { writer.write(characters, 0, readCount); } count = 0; index = 0; pos = 0; write(b); } } public void flush() throws IOException { int readCount = reader.read(characters); if (readCount > 0) { writer.write(characters, 0, readCount); } count = 0; index = 0; pos = 0; } } } /** * An interface to be implemented by encryption service providers. * @since 2.2.0 */ interface Cipher { /** * Encrypts the specified output stream. * @param outputStream * @return an encrypted output stream */ OutputStream encrypt(OutputStream outputStream) throws Exception; /** * This method is invoked after the encrypted output stream is used * allowing the Cipher implementation to do any maintenance work required, * such as flushing an internal cache. * @param outputStream the encrypted stream returned by {@link #encrypt(OutputStream)}. */ void finish(OutputStream outputStream) throws Exception; /** * Decrypts the specified input stream. * @param inputStream * @return a decrypted input stream */ InputStream decrypt(InputStream inputStream) throws Exception; /** * This method is invoked after the decrypted input stream is used * allowing the Cipher implementation to do any maintenance work required, * such as flushing internal cache. * @param inputStream the stream returned by {@link #decrypt(InputStream)}. */ void finish(InputStream inputStream) throws Exception; } /** * Deletes the contents of the given URI. * @param uri the URI to consider. * @param options options to influence how the contents are deleted, or <code>null</code> if there are no options. * @throws IOException if there is a problem deleting the contents. * @since 2.4 */ void delete(URI uri, Map<?, ?> options) throws IOException; /** * Returns a map from String properties to their corresponding values representing a description the given URI's contents. * See the {@link ContentHandler#contentDescription(URI, InputStream, Map, Map) content handler} for more details. * @param uri the URI to consider. * @param options options to influence how the content description is determined, or <code>null</code> if there are no options. * @return a map from String properties to their corresponding values representing a description the given URI's contents. * @throws IOException if there is a problem accessing the contents. * @see ContentHandler#contentDescription(URI, InputStream, Map, Map) * @since 2.4 */ Map<String, ?> contentDescription(URI uri, Map<?, ?> options) throws IOException; /** * Returns whether the given URI has contents. * If the URI {@link #exists(URI, Map) exists} * it will be possible to {@link #createOutputStream(URI, Map) create} an input stream. * @param uri the URI to consider. * @param options options to influence how the existence determined, or <code>null</code> if there are no options. * @return whether the given URI has contents. * @since 2.4 */ boolean exists(URI uri, Map<?, ?> options); /** * The time stamp {@link #getAttributes(URI, Map) attribute} representing the last time the contents of a URI were modified. * The value is represented as Long that encodes the number of milliseconds * since the epoch 00:00:00 GMT, January 1, 1970. * @since 2.4 */ String ATTRIBUTE_TIME_STAMP = "timeStamp"; /** * A {@link #ATTRIBUTE_TIME_STAMP} value that indicates no time stamp is available. * @since 2.4 */ long NULL_TIME_STAMP = -1; /** * The length {@link #getAttributes(URI, Map) attribute} representing the number of bytes in the contents of a URI. * It is represented as a Long value. * @since 2.4 */ String ATTRIBUTE_LENGTH = "length"; /** * The read only {@link #getAttributes(URI, Map) attribute} representing whether the contents of a URI can be modified. * It is represented as a Boolean value. * If the URI's contents {@link #exists(URI, Map) exist} and it is read only, * it will not be possible to {@link #createOutputStream(URI, Map) create} an output stream. * @since 2.4 */ String ATTRIBUTE_READ_ONLY = "readOnly"; /** * The execute {@link #getAttributes(URI, Map) attribute} representing whether the contents of a URI can be executed. * It is represented as a Boolean value. * @since 2.4 */ String ATTRIBUTE_EXECUTABLE = "executable"; /** * The archive {@link #getAttributes(URI, Map) attribute} representing whether the contents of a URI are archived. * It is represented as a Boolean value. * @since 2.4 */ String ATTRIBUTE_ARCHIVE = "archive"; /** * The hidden {@link #getAttributes(URI, Map) attribute} representing whether the URI is visible. * It is represented as a Boolean value. * @since 2.4 */ String ATTRIBUTE_HIDDEN = "hidden"; /** * The directory {@link #getAttributes(URI, Map) attribute} representing whether the URI represents a directory rather than a file. * It is represented as a Boolean value. * @since 2.4 */ String ATTRIBUTE_DIRECTORY = "directory"; /** * An option passed to a {@link Set Set<String>} to {@link #getAttributes(URI, Map)} to indicate the specific attributes to be fetched. */ String OPTION_REQUESTED_ATTRIBUTES = "requestedAttributes"; /** * Returns a map from String attributes to their corresponding values representing information about various aspects of the URI's state. * The {@link #OPTION_REQUESTED_ATTRIBUTES requested attributes option} can be used to specify which properties to fetch; * without that option, all supported attributes will be fetched. * If the URI doesn't not support any particular attribute, an entry for that attribute will not be appear in the result. * @param uri the URI to consider. * @param options options to influence how the attributes are determined, or <code>null</code> if there are no options. * @return a map from String attributes to their corresponding values representing information about various aspects of the URI's state. */ Map<String, ?> getAttributes(URI uri, Map<?, ?> options); /** * Updates the map from String attributes to their corresponding values representing information about various aspects of the URI's state. * Unsupported or unchangeable attributes are ignored. * @param uri the URI to consider. * @param attributes the new values for the attributes. * @param options options to influence how the attributes are updated, or <code>null</code> if there are no options. * @throws IOException if there is a problem updating the attributes. */ void setAttributes(URI uri, Map<String, ?> attributes, Map<?, ?> options) throws IOException; /** * The global static URI converter instance. * It's generally not a good idea to modify any aspect of this instance. * Instead, use a resource set's {@link ResourceSet#getURIConverter() local} instance. * @since 2.4 */ URIConverter INSTANCE = new ExtensibleURIConverterImpl(); }