eu.esdihumboldt.hale.common.core.io.HaleIO.java Source code

Java tutorial

Introduction

Here is the source code for eu.esdihumboldt.hale.common.core.io.HaleIO.java

Source

/*
 * Copyright (c) 2012 Data Harmonisation Panel
 * 
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution. If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     HUMBOLDT EU Integrated Project #030962
 *     Data Harmonisation Panel <http://www.dhpanel.eu>
 */

package eu.esdihumboldt.hale.common.core.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.xml.namespace.QName;

import org.apache.commons.io.FilenameUtils;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.w3c.dom.Element;

import com.google.common.base.Preconditions;
import com.google.common.io.InputSupplier;

import de.cs3d.util.eclipse.extension.ExtensionObjectFactoryCollection;
import de.cs3d.util.eclipse.extension.FactoryFilter;
import de.cs3d.util.logging.ALogger;
import de.cs3d.util.logging.ALoggerFactory;
import eu.esdihumboldt.hale.common.core.io.extension.ComplexValueDefinition;
import eu.esdihumboldt.hale.common.core.io.extension.ComplexValueExtension;
import eu.esdihumboldt.hale.common.core.io.extension.IOProviderDescriptor;
import eu.esdihumboldt.hale.common.core.io.extension.IOProviderExtension;
import eu.esdihumboldt.hale.common.core.io.supplier.LookupStreamResource;
import eu.esdihumboldt.util.Pair;

/**
 * Hale I/O utilities
 * 
 * @author Simon Templer
 * @partner 01 / Fraunhofer Institute for Computer Graphics Research
 * @since 2.5
 */
public abstract class HaleIO {

    private static final ALogger log = ALoggerFactory.getLogger(HaleIO.class);

    /**
     * Filter I/O provider factories by content type
     * 
     * @param factories the I/O provider factories
     * @param contentType the content type factories must support
     * @return provider factories that support the given content type
     */
    public static List<IOProviderDescriptor> filterFactories(Collection<IOProviderDescriptor> factories,
            IContentType contentType) {
        List<IOProviderDescriptor> result = new ArrayList<IOProviderDescriptor>();

        for (IOProviderDescriptor factory : factories) {
            Set<IContentType> supportedTypes = factory.getSupportedTypes();

            // check if contentType is supported
            for (IContentType test : supportedTypes) {
                if (isCompatibleContentType(test, contentType)) {
                    result.add(factory);
                    break;
                }
            }
        }

        return result;
    }

    /**
     * Find the content types that match the given file name and/or input.
     * 
     * NOTE: The implementation should try to restrict the result to one content
     * type and only use the input supplier if absolutely needed.
     * 
     * @param types the types to match
     * @param in the input supplier to use for testing, may be <code>null</code>
     *            if the file name is not <code>null</code>
     * @param filename the file name, may be <code>null</code> if the input
     *            supplier is not <code>null</code>
     * @return the matched content types
     */
    public static List<IContentType> findContentTypesFor(Collection<IContentType> types,
            InputSupplier<? extends InputStream> in, String filename) {
        Preconditions.checkArgument(filename != null || in != null,
                "At least one of input supplier and file name must not be null");

        List<IContentType> results = new ArrayList<IContentType>();

        if (filename != null) {
            // test file extension
            for (IContentType type : types) {
                String ext = FilenameUtils.getExtension(filename);
                if (ext != null && !ext.isEmpty()) {
                    String[] extensions = type.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
                    boolean match = false;
                    for (int i = 0; i < extensions.length && !match; i++) {
                        if (extensions[i].equalsIgnoreCase(ext)) {
                            match = true;
                        }
                    }
                    if (match) {
                        results.add(type);
                    }
                }
            }
        }

        if ((results.isEmpty() || results.size() > 1) && in != null) {
            // clear results because only an ambiguous result was found
            results.clear();
            // use input stream to make a better test
            IContentTypeManager ctm = Platform.getContentTypeManager();
            try {
                InputStream is = in.getInput();

                /*
                 * XXX IContentTypeManager.findContentTypes seems to return all
                 * kind of content types that match in any way, but ordered by
                 * relevance - so if all but the allowed types are removed, the
                 * remaining types may be very irrelevant and not a match that
                 * actually was determined based on the input stream.
                 * 
                 * XXX thus findContentTypesFor should not be used or only
                 * relied upon the single best match that is returned
                 */
                //            IContentType[] candidates = ctm.findContentTypesFor(is, filename);
                //
                //            for (IContentType candidate : candidates) {
                //               if (types.contains(candidate)) {
                //                  results.add(candidate);
                //               }
                //            }
                // instead use findContentTypeFor
                IContentType candidate = ctm.findContentTypeFor(is, null);
                if (types.contains(candidate)) {
                    results.add(candidate);
                }

                is.close();
            } catch (IOException e) {
                log.warn("Could not read input to determine content type", e);
            }
        }

        return results;
    }

    /**
     * Get the I/O provider factories of a certain type
     * 
     * @param providerType the provider type, usually an interface
     * @return the factories currently registered in the system
     */
    public static <P extends IOProvider> Collection<IOProviderDescriptor> getProviderFactories(
            final Class<P> providerType) {
        return IOProviderExtension.getInstance()
                .getFactories(new FactoryFilter<IOProvider, IOProviderDescriptor>() {

                    @Override
                    public boolean acceptFactory(IOProviderDescriptor descriptor) {
                        return providerType.isAssignableFrom(descriptor.getProviderType());
                    }

                    @Override
                    public boolean acceptCollection(
                            ExtensionObjectFactoryCollection<IOProvider, IOProviderDescriptor> collection) {
                        return true;
                    }
                });
    }

    /**
     * Find an I/O provider factory
     * 
     * @param <P> the provider interface type
     * 
     * @param providerType the provider type, usually an interface
     * @param contentType the content type the provider must match, may be
     *            <code>null</code> if providerId is set
     * @param providerId the id of the provider to use, may be <code>null</code>
     *            if contentType is set
     * @return the I/O provider factory or <code>null</code> if no matching I/O
     *         provider factory is found
     */
    public static <P extends IOProvider> IOProviderDescriptor findIOProviderFactory(Class<P> providerType,
            IContentType contentType, String providerId) {
        Preconditions.checkArgument(contentType != null || providerId != null);

        Collection<IOProviderDescriptor> factories = getProviderFactories(providerType);
        if (contentType != null) {
            factories = filterFactories(factories, contentType);
        }

        IOProviderDescriptor result = null;

        if (providerId != null) {
            for (IOProviderDescriptor factory : factories) {
                if (factory.getIdentifier().equals(providerId)) {
                    result = factory;
                    break;
                }
            }
        } else {
            // TODO choose priority based?
            if (!factories.isEmpty()) {
                result = factories.iterator().next();
            }
        }

        return result;
    }

    /**
     * Creates an I/O provider instance
     * 
     * @param <P> the provider interface type
     * 
     * @param providerType the provider type, usually an interface
     * @param contentType the content type the provider must match, may be
     *            <code>null</code> if providerId is set
     * @param providerId the id of the provider to use, may be <code>null</code>
     *            if contentType is set
     * @return the I/O provider preconfigured with the content type if it was
     *         given or <code>null</code> if no matching I/O provider is found
     */
    @SuppressWarnings("unchecked")
    public static <P extends IOProvider> P createIOProvider(Class<P> providerType, IContentType contentType,
            String providerId) {
        IOProviderDescriptor factory = findIOProviderFactory(providerType, contentType, providerId);
        P result;
        try {
            result = (P) ((factory == null) ? (null) : (factory.createExtensionObject()));
        } catch (Exception e) {
            throw new RuntimeException("Could not create I/O provider", e);
        }

        if (result != null && contentType != null) {
            result.setContentType(contentType);
        }

        return result;
    }

    /**
     * Find the content type for the given input
     * 
     * @param <P> the provider interface type
     * 
     * @param providerType the provider type, usually an interface
     * @param in the input supplier to use for testing, may be <code>null</code>
     *            if the file name is not <code>null</code>
     * @param filename the file name, may be <code>null</code> if the input
     *            supplier is not <code>null</code>
     * @return the content type or <code>null</code> if no matching content type
     *         is found
     */
    public static <P extends IOProvider> IContentType findContentType(Class<P> providerType,
            InputSupplier<? extends InputStream> in, String filename) {
        Collection<IOProviderDescriptor> providers = getProviderFactories(providerType);

        // collect supported content types
        Set<IContentType> supportedTypes = new HashSet<IContentType>();
        for (IOProviderDescriptor factory : providers) {
            supportedTypes.addAll(factory.getSupportedTypes());
        }

        // find matching content type
        List<IContentType> types = findContentTypesFor(supportedTypes, in, filename);

        if (types == null || types.isEmpty()) {
            return null;
        }

        // TODO choose?
        return types.iterator().next();
    }

    /**
     * Find an I/O provider instance for the given input
     * 
     * @param <P> the provider interface type
     * 
     * @param providerType the provider type, usually an interface
     * @param in the input supplier to use for testing, may be <code>null</code>
     *            if the file name is not <code>null</code>
     * @param filename the file name, may be <code>null</code> if the input
     *            supplier is not <code>null</code>
     * @return the I/O provider or <code>null</code> if no matching I/O provider
     *         is found
     */
    public static <P extends IOProvider> P findIOProvider(Class<P> providerType,
            InputSupplier<? extends InputStream> in, String filename) {
        IContentType contentType = findContentType(providerType, in, filename);
        if (contentType == null) {
            return null;
        }

        return HaleIO.createIOProvider(providerType, contentType, null);
    }

    /**
     * Find an I/O provider instance for the given input
     * 
     * @param <T> the provider interface type
     * 
     * @param providerType the provider type, usually an interface
     * @param in the input supplier to use for testing, may be <code>null</code>
     *            if the file name is not <code>null</code>
     * @param filename the file name, may be <code>null</code> if the input
     *            supplier is not <code>null</code>
     * @return a pair with the I/O provider and the corresponding identifier,
     *         both are <code>null</code> if no matching I/O provider was found
     */
    @SuppressWarnings("unchecked")
    public static <T extends IOProvider> Pair<T, String> findIOProviderAndId(Class<T> providerType,
            InputSupplier<? extends InputStream> in, String filename) {
        T reader = null;
        String providerId = null;
        IContentType contentType = HaleIO.findContentType(providerType, in, filename);
        if (contentType != null) {
            IOProviderDescriptor factory = HaleIO.findIOProviderFactory(providerType, contentType, null);
            try {
                reader = (T) factory.createExtensionObject();
                providerId = factory.getIdentifier();
            } catch (Exception e) {
                throw new RuntimeException("Could not create I/O provider", e);
            }

            if (reader != null) {
                reader.setContentType(contentType);
            }
        }
        return new Pair<T, String>(reader, providerId);
    }

    /**
     * Automatically find an import provider to load a resource that is
     * available through an input stream that can only be read once.
     * 
     * @param type the import provider type
     * @param in the input stream
     * @return the import provider or <code>null</code> if none was found
     */
    public static <T extends ImportProvider> T findImportProvider(Class<T> type, InputStream in) {
        LookupStreamResource res = new LookupStreamResource(in, null, 64 * 1024);
        T provider = HaleIO.findIOProvider(type, res.getLookupSupplier(), null);
        if (provider != null) {
            provider.setSource(res.getInputSupplier());
            return provider;
        }
        return null;
    }

    /**
     * Test if the given value content type is compatible with the given parent
     * content type
     * 
     * @param parentType the parent content type
     * @param valueType the value content type
     * @return if the value content type is compatible with the parent content
     *         type
     */
    public static boolean isCompatibleContentType(IContentType parentType, IContentType valueType) {
        return valueType.isKindOf(parentType);
    }

    /**
     * Get the value of a complex property represented as a DOM element.
     * 
     * @param element the DOM element
     * @param context the context object, may be <code>null</code>
     * @return the complex value converted through the
     *         {@link ComplexValueExtension}, or the original element
     */
    public static Object getComplexValue(Element element, Object context) {
        QName name;
        if (element.getNamespaceURI() != null && !element.getNamespaceURI().isEmpty()) {
            name = new QName(element.getNamespaceURI(), element.getLocalName());
        } else {
            name = new QName(element.getTagName()); // .getLocalName());
        }
        ComplexValueDefinition cvt = ComplexValueExtension.getInstance().getDefinition(name);
        if (cvt != null) {
            // create and return the complex parameter value
            return cvt.fromDOM(element, context);
        }

        // the element itself is the complex value
        return element;
    }

    /**
     * Get the value of a complex property represented as a DOM element.
     * 
     * @param element the DOM element
     * @param expectedType the expected parameter type, this must be either
     *            {@link String}, DOM {@link Element} or a complex value type
     *            defined in the {@link ComplexValueExtension}
     * @param context the context object, may be <code>null</code>
     * @return the complex value or <code>null</code> if it could not be created
     *         from the element
     */
    @SuppressWarnings("unchecked")
    public static <T> T getComplexValue(Element element, Class<T> expectedType, Object context) {
        if (element == null) {
            return null;
        }

        QName name;
        if (element.getNamespaceURI() != null && !element.getNamespaceURI().isEmpty()) {
            name = new QName(element.getNamespaceURI(), element.getLocalName());
        } else {
            String ln = element.getTagName(); // .getLocalName();
            name = new QName(ln);
        }
        ComplexValueDefinition cvt = ComplexValueExtension.getInstance().getDefinition(name);
        Object value = null;
        if (cvt != null) {
            try {
                value = cvt.fromDOM(element, context);
            } catch (Exception e) {
                throw new IllegalStateException("Failed to load complex value from DOM", e);
            }
        }

        if (value != null && expectedType.isAssignableFrom(value.getClass())) {
            return (T) value;
        }

        // maybe the element itself is OK
        if (expectedType.isAssignableFrom(element.getClass())) {
            return (T) element;
        }

        if (expectedType.isAssignableFrom(String.class)) {
            // FIXME use legacy complex value if possible
        }

        return null;
    }

    /**
     * Get the representation of a complex value as a DOM element. Uses the
     * {@link ComplexValueExtension}.
     * 
     * @param value the complex value
     * @return the DOM representation
     * @throws IllegalStateException if the value is neither a DOM element nor
     *             can be converted to one using the
     *             {@link ComplexValueExtension}
     */
    public static Element getComplexElement(Object value) {
        if (value instanceof Element) {
            // as is
            return (Element) value;
        }

        ComplexValueDefinition cvd = ComplexValueExtension.getInstance().getDefinition(value.getClass());
        if (cvd != null) {
            return cvd.toDOM(value);
        }

        throw new IllegalStateException("No definition for complex parameter value found");
    }

    //   /**
    //    * Get the file extensions for the given content type
    //    * 
    //    * @param contentType the content type
    //    * @param prefix the prefix to add before the extensions, e.g. "." or "*.",
    //    *   may be <code>null</code>
    //    * @return the file extensions or <code>null</code>
    //    */
    //   public static String[] getFileExtensions(ContentType contentType,
    //         String prefix) {
    //      SortedSet<String> exts = new TreeSet<String>();
    //      
    //      ContentTypeService cts = OsgiUtils.getService(ContentTypeService.class);
    //      String[] typeExts = cts.getFileExtensions(contentType);
    //      if (typeExts != null) {
    //         for (String typeExt : typeExts) {
    //            if (prefix == null) {
    //               exts.add(typeExt);
    //            }
    //            else {
    //               exts.add(prefix + typeExt);
    //            }
    //         }
    //      }
    //      
    //      if (exts.isEmpty()) {
    //         return null;
    //      }
    //      else {
    //         return exts.toArray(new String[exts.size()]);
    //      }
    //   }

    //   /**
    //    * Get all file extensions for the given content types
    //    * 
    //    * @param contentTypes the content types
    //    * @param prefix the prefix to add before the extensions, e.g. "." or "*.",
    //    *   may be <code>null</code>
    //    * @return the file extensions or <code>null</code>
    //    */
    //   public static String[] getFileExtensions(Iterable<ContentType> contentTypes,
    //         String prefix) {
    //      SortedSet<String> exts = new TreeSet<String>();
    //      
    //      ContentTypeService cts = OsgiUtils.getService(ContentTypeService.class);
    //      for (ContentType contentType : contentTypes) {
    //         String[] typeExts = cts.getFileExtensions(contentType);
    //         if (typeExts != null) {
    //            for (String typeExt : typeExts) {
    //               if (prefix == null) {
    //                  exts.add(typeExt);
    //               }
    //               else {
    //                  exts.add(prefix + typeExt);
    //               }
    //            }
    //         }
    //      }
    //      
    //      if (exts.isEmpty()) {
    //         return null;
    //      }
    //      else {
    //         return exts.toArray(new String[exts.size()]);
    //      }
    //   }

}