net.rptools.assets.supplier.AbstractURIAssetSupplier.java Source code

Java tutorial

Introduction

Here is the source code for net.rptools.assets.supplier.AbstractURIAssetSupplier.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
 * applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
 * OF ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */
package net.rptools.assets.supplier;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;

import javax.imageio.ImageIO;

import net.rptools.assets.AssetComponentImpl;
import net.rptools.assets.AssetImpl;

import org.apache.commons.io.IOUtils;
import org.rptools.assets.Asset;
import org.rptools.assets.Asset.Type;
import org.rptools.assets.AssetListener;
import org.rptools.framework.ThreadPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Refactored class. Provides commonalities for URI asset suppliers.
 * @author username
 */
public abstract class AbstractURIAssetSupplier extends AbstractAssetSupplier {
    // Utility
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractURIAssetSupplier.class);

    // guessContentTypeFromStream only needs the first 12 bytes
    private static final int PUSHBACK_LIMIT = 20;

    // Default interval (ms)
    private static final long DEFAULT_NOTIFY_INTERVAL = 500;

    // Notify partial interval
    private long notifyInterval = DEFAULT_NOTIFY_INTERVAL; // millis

    // Location (meaning varies with subclass)
    private String location;

    /**
     * Constructor.
     * @param override properties to take precendence over default ones
     * @param component back reference
     */
    protected AbstractURIAssetSupplier(final AssetComponentImpl component, final Properties override) {
        super(component, override);
    }

    @Override
    public Asset get(final String id, final Type type, final AssetListener listener) {
        Asset result = null;
        try {
            final URI uri = new URI(getKnownAsset(id));
            LOGGER.info("Start loading id={}", id);
            result = load(id, type, uri, listener);
            LOGGER.info("Finished loading id={}", id);
        } catch (final URISyntaxException e) {
            LOGGER.error("Not an URL id={}", id, e);
            return null;
        }
        return result;
    }

    /**
     * Read the image, informing the listener once in a while.
     * @param id id to get
     * @param type type of asset
     * @param uri url to get
     * @param listener listener to inform
     * @return prepared image
     */
    @ThreadPolicy(ThreadPolicy.ThreadId.ANY)
    protected abstract AssetImpl load(String id, Type type, URI uri, AssetListener listener);

    /**
     * Direct reference getter, to be overloaded by subclasses.
     * @param id id of the asset
     * @return asset name associate to id. An absolute URI.
     */
    @ThreadPolicy(ThreadPolicy.ThreadId.ANY)
    protected abstract String getKnownAsset(String id);

    /**
     * Getter.
     * @return notify interval
     */
    @ThreadPolicy(ThreadPolicy.ThreadId.ANY)
    public long getNotifyInterval() {
        return notifyInterval;
    }

    /**
     * Setter.
     * @param aNotifyInterval notify interval
     */
    @ThreadPolicy(ThreadPolicy.ThreadId.ANY)
    public void setNotifyInterval(final long aNotifyInterval) {
        notifyInterval = aNotifyInterval;
    }

    @Override
    public String getLocation() {
        return location;
    }

    /**
     * This is a simple implementation that assumes the tags atr contained in
     * the URL name.
     * @param id id of asset
     * @param tag tag to check
     * @return whether the asset is tagged accordingly
     */
    @Override
    public boolean hasTag(final String id, final String tag) {
        final String url = getKnownAsset(id);
        if (url == null) {
            return false;
        }
        return url.contains(tag);
    }

    /**
     * Setter.
     * @param aLocation location
     */
    @ThreadPolicy(ThreadPolicy.ThreadId.ANY)
    public void setLocation(final String aLocation) {
        location = aLocation;
    }

    /**
     * Create an asset and determine its format.
     * @param id id of asset
     * @param type type of asset
     * @param listener listener to inform of (partial) completion
     * @param assetLength length, if known, or -1
     * @param stream stream to read
     * @return asset
     * @throws IOException I/O problems
     */
    @ThreadPolicy(ThreadPolicy.ThreadId.ANY)
    protected AssetImpl newAsset(final String id, final Type type, final AssetListener listener,
            final int assetLength, final InputStream stream) throws IOException {
        final InputStream input = new InputStreamInterceptor(getComponent().getFramework(), id, assetLength, stream,
                listener, getNotifyInterval());
        final PushbackInputStream pushbackStream = new PushbackInputStream(input, PUSHBACK_LIMIT);
        final byte[] firstBytes = new byte[PUSHBACK_LIMIT];
        final int actualLength = pushbackStream.read(firstBytes);
        if (actualLength != -1) {
            pushbackStream.unread(firstBytes, 0, actualLength);
        }

        final ByteArrayInputStream bais = new ByteArrayInputStream(firstBytes);
        final String mimeType = URLConnection.guessContentTypeFromStream(bais);
        LOGGER.debug("mimeType={}, actualLength={}", mimeType, actualLength);

        // read in image
        AssetImpl asset = null;
        switch (type) {
        case IMAGE:
            asset = new AssetImpl(new Image(pushbackStream));
            asset.setMimetype(mimeType);
            break;
        case TEXT:
            asset = new AssetImpl(IOUtils.toString(pushbackStream, StandardCharsets.UTF_8.name()));
            asset.setMimetype(mimeType);
            break;
        default:
            break;
        }
        if (listener != null) {
            listener.notify(id, asset);
        }
        return asset;
    }

    /**
     * Handle output stream and notify the asset upon completion. <b>This closes
     * the stream.</b>
     * @param id id of asset
     * @param obj asset itself
     * @param listener listener to inform upon completion
     * @param stream output stream to write to. Will be closed.
     * @throws IOException I/O problems
     */
    @ThreadPolicy(ThreadPolicy.ThreadId.ANY)
    protected void handleOutputStream(final String id, final Asset obj, final AssetListener listener,
            final OutputStream stream) throws IOException {
        try (final OutputStream outputStream = new OutputStreamInterceptor(getComponent().getFramework(), id, -1L,
                stream, listener, getNotifyInterval())) {
            switch (obj.getType()) {
            case IMAGE:
                final Image img = (Image) obj.getMain();
                ImageIO.write(SwingFXUtils.fromFXImage(img, null), "PNG", outputStream);
                break;
            case TEXT:
                final String txt = (String) obj.getMain();
                outputStream.write(txt.getBytes(StandardCharsets.UTF_8));
                break;
            default: // This should not happen. Messing with the constants, renders assets non-writable
                break;
            }
        }
        if (listener != null) {
            listener.notify(id, obj);
        }
    }

    @Override
    public boolean canCreate(final Type clazz) {
        return false;
    }

    @Override
    public String create(final Asset obj, final AssetListener listener) {
        return null;
    }

    @Override
    public boolean canRemove(final String id) {
        return false;
    }

    @Override
    public boolean remove(final String id) {
        // We do not throw an exception; the result code is sufficient
        return false;
    }

    @Override
    public void update(final String id, final Asset obj, final AssetListener listener) {
    }
}