Java tutorial
/* * 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) { } }