/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.document;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.TextMessageElement;
import net.jxta.logging.Logging;
import net.jxta.util.ClassFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A factory for constructing instances of {@link StructuredDocument}.
* Behind the scenes, it also provides for the registration of the mime-types
* and constructors needed to accomplish the construction. All supported
* mime-types will need to register their implementation in this factory.
*
* @see net.jxta.document.Document
* @see net.jxta.document.StructuredTextDocument
* @see net.jxta.document.StructuredDocument
* @see net.jxta.document.MimeMediaType
*/
public final class StructuredDocumentFactory extends ClassFactory<MimeMediaType, StructuredDocumentFactory.Instantiator> {
/**
* Log4J Logger
*/
private static final Logger LOG = Logger.getLogger(StructuredDocumentFactory.class.getName());
/**
* Interface for instantiators of StructuredDocuments
*/
public interface Instantiator {
/**
* For mapping between extensions and MIME types.
*/
class ExtensionMapping {
/**
* The extension
*/
private final String extension;
/**
* MIME type it maps to
*/
private final MimeMediaType mimetype;
/**
* default constructor
*
* @param extension The extension
* @param mimetype MIME type it maps to
*/
public ExtensionMapping(String extension, MimeMediaType mimetype) {
this.extension = extension;
this.mimetype = (null != mimetype) ? mimetype.intern() : null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object target) {
if (this == target) {
return true;
}
if (target instanceof ExtensionMapping) {
ExtensionMapping likeMe = (ExtensionMapping) target;
if (!extension.equals(likeMe.extension)) {
return false;
}
if ((null == mimetype) && (null == likeMe.mimetype)) {
return true;
}
if ((null == mimetype) || (null == likeMe.mimetype)) {
return false;
}
return mimetype.equals(likeMe.mimetype);
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hash = extension.hashCode();
if (null != mimetype) {
hash ^= mimetype.hashCode();
}
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return extension + " -> " + ((null != mimetype) ? mimetype.toString() : "<null>");
}
/**
* Returns the extension which is part of this mapping.
*
* @return the extension which is part of this mapping.
*/
public String getExtension() {
return extension;
}
/**
* Returns the MIME Media Type which is part of this mapping.
*
* @return the MIME Media Type which is part of this mapping.
*/
public MimeMediaType getMimeMediaType() {
return mimetype;
}
}
/**
* Returns the MIME Media types supported by this this Document per
* {@link <a href="http://www.ietf.org/rfc/rfc2046.txt">IETF RFC 2046 <i>MIME : Media Types</i></a>}.
*
* <p/>JXTA does not currently support the 'Multipart' or 'Message'
* media types.
*
* @return An array of MimeMediaType objects containing the MIME Media Type
* for this Document.
*/
MimeMediaType[] getSupportedMimeTypes();
/**
* Returns the mapping of file extension and mime-types for this type
* of document. The default extension is mapped to the 'null' mime-type
* and should only be used if no other mapping matches.
*
* @return An array of objects containing file extensions
*/
ExtensionMapping[] getSupportedFileExtensions();
/**
* Create a new structured document of the type specified by doctype.
*
* @param mimeType The MIME type to be associated with this instance.
* the base type must be one of the types returned by
* <tt>getSupportedMimeTypes</tt>. Some implementations may accept
* parameters in the params section of the MIME type.
* @param doctype Type for the base node of the document.
* @return StructuredDocument instance.
*/
StructuredDocument newInstance(MimeMediaType mimeType, String doctype);
/**
* Create a new structured document of the type specified by doctype.
*
* @param mimeType The MIME type to be associated with this instance.
* The base type must be one of the types returned by
* <tt>getSupportedMimeTypes</tt>. Some implementations may accept
* parameters in the params section of the MIME type.
* @param doctype Type for the base node of the document.
* @param value Value for the base node of the document.
* @return {@link StructuredDocument} instance.
*/
StructuredDocument newInstance(MimeMediaType mimeType, String doctype, String value);
/**
* Create a structured document from a stream containing an appropriately serialized
* instance of the same document.
*
* @param mimeType The MIME type to be associated with this instance.
* The base type must be one of the types returned by
* <tt>getSupportedMimeTypes</tt>. Some implementations may accept
* parameters in the params section of the MIME type.
* @param source The {@code Inputstream} from which to read the
* document.
* @return {@link StructuredDocument} instance.
* @throws IOException Thrown for problems reading from the source.
*/
StructuredDocument newInstance(MimeMediaType mimeType, InputStream source) throws IOException;
}
/**
* Interface for instantiators of StructuredTextDocuments
*/
public interface TextInstantiator extends Instantiator {
/**
* Create a structured document from a Reader containing an appropriately serialized
* instance of the same document.
*
* @param mimeType The MIME type to be associated with this instance.
* The base type must be one of the types returned by
* <tt>getSupportedMimeTypes</tt>. Some implementations may accept
* parameters in the params section of the MIME type.
* @param source {@code Reader} from which to read the instance.
* @return {@link StructuredDocument} instance.
* @throws IOException Thrown for problems reading from the source.
*/
StructuredDocument newInstance(MimeMediaType mimeType, Reader source) throws IOException;
}
/**
* This class is a singleton. This is the instance that backs the
* static methods.
*/
private static final StructuredDocumentFactory factory = new StructuredDocumentFactory();
/**
* This is the map of mime-types and instantiators used by
* <CODE>newStructuredDocument</CODE>.
*/
private final Map<MimeMediaType, Instantiator> encodings = new HashMap<MimeMediaType, Instantiator>();
/**
* This is the map of extensions to mime-types used by
* {@link #getMimeTypeForFileExtension(String) }
*/
private final Map<String, MimeMediaType> extToMime = new HashMap<String, MimeMediaType>();
/**
* This is the map of mime-types to extensions used by
* {@link #getFileExtensionForMimeType(MimeMediaType mimetype) }
*/
private final Map<MimeMediaType, String> mimeToExt = new HashMap<MimeMediaType, String>();
/**
* If true then the pre-defined set of StructuredDocument sub-classes has
* been registered from the property containing them.
*/
private boolean loadedProperty = false;
/**
* Private constructor. This class is not meant to be instantiated except
* by itself.
*
*/
private StructuredDocumentFactory() {}
/**
* Registers the pre-defined set of StructuredDocument sub-classes so that
* this factory can construct them.
*
* @return true if at least one of the StructuredDocument sub-classes could
* be registered otherwise false.
*/
private synchronized boolean loadProviders() {
if (factory.loadedProperty) {
return true;
}
factory.loadedProperty = registerProviders(StructuredDocument.class.getName());
return factory.loadedProperty;
}
/**
* {@inheritDoc}
*/
@Override
protected Map<MimeMediaType, Instantiator> getAssocTable() {
return encodings;
}
/**
* {@inheritDoc}
*/
@Override
protected Class<MimeMediaType> getClassForKey() {
return MimeMediaType.class;
}
/**
* {@inheritDoc}
*/
@Override
protected Class<Instantiator> getClassOfInstantiators() {
// our key is the doctype names.
return Instantiator.class;
}
/**
* {@inheritDoc}
*
* <p/>We override the standard implementation to get the MIME type from
* the class and use that as the key to register the class with the factory.
*
* @param className The class name which will be registered.
* @return boolean true if the class was registered otherwise false.
*/
@Override
protected boolean registerAssoc(String className) {
boolean registeredSomething = false;
LOG.finer("Registering : " + className);
try {
Class docClass = Class.forName(className);
Instantiator instantiator = (Instantiator) docClass.getField("INSTANTIATOR").get(null);
MimeMediaType[] mimeTypes = instantiator.getSupportedMimeTypes();
for (MimeMediaType mimeType : mimeTypes) {
LOG.finer(" Registering Type : " + mimeType.getMimeMediaType());
registeredSomething |= registerInstantiator(mimeType, instantiator);
}
} catch (Exception all) {
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.log(Level.WARNING, "Failed to register \'" + className + "\'", all);
}
}
return registeredSomething;
}
/**
* Returns the preferred extension for a given mime-type. If there is no
* mapping or no preferred extension for this MIME type then <tt>null</tt> is
* returned.
*
* @param mimetype the MimeMediaType we wish to know the file extension for.
* @return String containing the extension or null for mime-types with no
* known association.
*/
public static String getFileExtensionForMimeType(MimeMediaType mimetype) {
factory.loadProviders();
return factory.mimeToExt.get(mimetype.getBaseMimeMediaType());
}
/**
* Returns the preferred mime-type for a given file extension. If there is
* no mapping then <tt>null</tt> is returned.
*
* @param extension The extension we wish to know the mime-type for.
* @return MimeMediaType associated with this file extension.
*/
public static MimeMediaType getMimeTypeForFileExtension(String extension) {
factory.loadProviders();
MimeMediaType result = factory.extToMime.get(extension);
return result;
}
/**
* Register an instantiator object a mime-type of documents to be
* constructed.
*
* @param mimetype the mime-type associated.
* @param instantiator the instantiator that wants to be registered..
* @return boolean true if the instantiator for this mime-type is now
* registered. If there was already an instantiator this mime-type then
* false will be returned.
* @throws SecurityException there were permission problems registering
* the instantiator.
*/
public static boolean registerInstantiator(MimeMediaType mimetype, Instantiator instantiator) {
boolean registered = factory.registerAssoc(mimetype.getBaseMimeMediaType(), instantiator);
if (registered) {
Instantiator.ExtensionMapping[] extensions = instantiator.getSupportedFileExtensions();
for (int eachExt = 0; eachExt < extensions.length; eachExt++) {
if (null != extensions[eachExt].getMimeMediaType()) {
factory.extToMime.put(extensions[eachExt].getExtension(), extensions[eachExt].getMimeMediaType().intern());
factory.mimeToExt.put(extensions[eachExt].getMimeMediaType(), extensions[eachExt].getExtension());
// And the base version.
factory.mimeToExt.put(extensions[eachExt].getMimeMediaType().getBaseMimeMediaType(), extensions[eachExt].getExtension());
}
}
}
return registered;
}
/**
* Constructs an instance of {@link StructuredDocument} matching
* the mime-type specified by the <CODE>mimetype</CODE> parameter. The
* <CODE>doctype</CODE> parameter identifies the base type of the
* {@link StructuredDocument}.
*
* @param mimetype Specifies the mime media type to be associated with
* the {@link StructuredDocument} to be created.
* @param doctype Specifies the root type of the {@link StructuredDocument}
* to be created.
* @return StructuredDocument The instance of {@link StructuredDocument}
* or null if it could not be created.
* @throws java.util.NoSuchElementException invalid mime-media-type
*/
public static StructuredDocument newStructuredDocument(MimeMediaType mimetype, String doctype) {
factory.loadProviders();
Instantiator instantiator = factory.getInstantiator(mimetype.getBaseMimeMediaType());
return instantiator.newInstance(mimetype, doctype);
}
/**
* Constructs an instance of {@link StructuredDocument} matching
* the mime-type specified by the <CODE>mimetype</CODE> parameter. The
* <CODE>doctype</CODE> parameter identifies the base type of the
* {@link StructuredDocument}. Value supplies a value for the root
* element.
*
* @param mimetype Specifies the mime media type to be associated with
* the {@link StructuredDocument} to be created.
* @param doctype Specifies the root type of the {@link StructuredDocument}
* to be created.
* @param value Specifies a value for the root element.
* @return StructuredDocument The instance of {@link StructuredDocument}
* or null if it could not be created.
* @throws java.util.NoSuchElementException if the mime-type has not been registered.
*/
public static StructuredDocument newStructuredDocument(MimeMediaType mimetype, String doctype, String value) {
factory.loadProviders();
Instantiator instantiator = factory.getInstantiator(mimetype.getBaseMimeMediaType());
return instantiator.newInstance(mimetype, doctype, value);
}
/**
* Constructs an instance of {@link StructuredDocument} matching
* the mime-type specified by the <CODE>mimetype</CODE> parameter. The
* <CODE>doctype</CODE> parameter identifies the base type of the
* {@link StructuredDocument}.
*
* @param mimetype Specifies the mime media type to be associated with the
* {@link StructuredDocument} to be created.
* @param stream Contains an InputStream from which the document will be
* constructed.
* @return StructuredDocument The instance of {@link StructuredDocument}
* or null if it could not be created.
* @throws IOException If there is a problem reading from the stream.
* @throws java.util.NoSuchElementException if the mime-type has not been registered.
*/
public static StructuredDocument newStructuredDocument(MimeMediaType mimetype, InputStream stream) throws IOException {
factory.loadProviders();
Instantiator instantiator = factory.getInstantiator(mimetype.getBaseMimeMediaType());
return instantiator.newInstance(mimetype, stream);
}
/**
* Constructs an instance of {@link StructuredDocument} matching
* the mime-type specified by the <CODE>mimetype</CODE> parameter. The
* <CODE>doctype</CODE> parameter identifies the base type of the
* {@link StructuredDocument}.
*
* @param mimetype Specifies the mime media type to be associated with the
* {@link StructuredDocument} to be created.
* @param reader A Reader from which the document will be constructed.
* @return StructuredDocument The instance of {@link StructuredDocument}
* or {@code null} if it could not be created.
* @throws IOException If there is a problem reading from the stream.
* @throws java.util.NoSuchElementException if the mime-type has not been registered.
* @throws UnsupportedOperationException if the mime-type provided is not
* a text oriented MIME type.
*/
public static StructuredDocument newStructuredDocument(MimeMediaType mimetype, Reader reader) throws IOException {
factory.loadProviders();
Instantiator instantiator = factory.getInstantiator(mimetype.getBaseMimeMediaType());
if (!(instantiator instanceof TextInstantiator)) {
// XXX 20020502 bondolo@jxta.org we could probably do something
// really inefficient that would allow it to work, but better not to.
// if ReaderInputStream existed, it would be easy to do.
if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
LOG.warning( "Document Class \'" + instantiator.getClass().getName() + "\' associated with \'" + mimetype
+ "\' is not a text oriented document");
}
throw new UnsupportedOperationException( "Document Class '" + instantiator.getClass().getName()
+ "' associated with '" + mimetype + "' is not a text oriented document");
}
return ((TextInstantiator) instantiator).newInstance(mimetype, reader);
}
/**
* Constructs an instance of {@link StructuredDocument} based upon the
* content of the provided message element.
*
* @param element The message element from which to create the document.
* @return StructuredDocument The instance of {@link StructuredDocument}
* or null if it could not be created.
* @throws IOException If there is a problem reading from the stream.
* @throws java.util.NoSuchElementException if the mime-type has not been registered.
*/
public static StructuredDocument newStructuredDocument(MessageElement element) throws IOException {
factory.loadProviders();
Instantiator instantiator = factory.getInstantiator(element.getMimeType().getBaseMimeMediaType());
if ((instantiator instanceof TextInstantiator) && (element instanceof TextMessageElement)) {
return ((TextInstantiator) instantiator).newInstance(element.getMimeType(), ((TextMessageElement) element).getReader());
} else {
return instantiator.newInstance(element.getMimeType(), element.getStream());
}
}
}