From 74bc4f280c55842f7a9de7e568652c3945692995 Mon Sep 17 00:00:00 2001 From: Merudo Date: Thu, 17 Oct 2019 17:20:27 -0500 Subject: [PATCH 1/2] Add playClip, stopClip and getClipProperties functions - playClip works much like playStream, but preloads the decompresssed sound in memory. This leads to better performances for short sounds. - playCount of 0: preload the sound - clips are "fire and forget", and can't be edited once played - The same clip can be played multiple times simultaneously - Close #810 --- .../net/rptools/lib/sound/SoundManager.java | 136 ++++++++++++++++-- .../client/functions/SoundFunctions.java | 29 +++- 2 files changed, 154 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/rptools/lib/sound/SoundManager.java b/src/main/java/net/rptools/lib/sound/SoundManager.java index 242b81f62b..4792aba8d0 100644 --- a/src/main/java/net/rptools/lib/sound/SoundManager.java +++ b/src/main/java/net/rptools/lib/sound/SoundManager.java @@ -24,14 +24,19 @@ import javafx.application.Platform; import javafx.scene.media.AudioClip; import net.rptools.maptool.client.functions.MediaPlayerAdapter; +import net.rptools.maptool.language.I18N; +import net.rptools.parser.ParserException; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; /** * This class stores AudioClip for system sounds and sound events. Event sounds are played through * the playSoundEvent method. */ public class SoundManager { - private final Map registeredSoundMap = new HashMap(); - private final Map soundEventMap = new HashMap(); + private static final Map registeredSoundMap = new HashMap(); + private static final Map soundEventMap = new HashMap(); + private static final Map userSounds = new HashMap(); /** * Loads the sound list and register the system sounds. @@ -39,7 +44,7 @@ public class SoundManager { * @param configPath The path for the sound resources * @throws IOException when configPath can't be read */ - public void configure(String configPath) throws IOException { + public static void configure(String configPath) throws IOException { Properties props = new Properties(); InputStream clipList = SoundManager.class.getClassLoader().getResourceAsStream(configPath); if (clipList == null) throw new IOException(); @@ -53,7 +58,7 @@ public void configure(String configPath) throws IOException { * @param properties the property file */ @SuppressWarnings("unchecked") - public void configure(Properties properties) { + public static void configure(Properties properties) { for (Enumeration e = (Enumeration) properties.propertyNames(); e.hasMoreElements(); ) { String key = e.nextElement(); @@ -67,10 +72,10 @@ public void configure(Properties properties) { * @param name the name of the sound * @param path the path to the sound */ - public void registerSound(String name, String path) { + public static void registerSound(String name, String path) { if (path != null && path.trim().length() == 0) path = null; - URL url = path != null ? getClass().getClassLoader().getResource(path) : null; + URL url = path != null ? SoundManager.class.getClassLoader().getResource(path) : null; AudioClip clip = url != null ? new AudioClip(url.toExternalForm()) : null; if (clip != null) registeredSoundMap.put(name, clip); @@ -83,7 +88,7 @@ public void registerSound(String name, String path) { * @param name the name of the sound * @return the audioclip of the sound */ - public AudioClip getRegisteredSound(String name) { + public static AudioClip getRegisteredSound(String name) { return registeredSoundMap.get(name); } @@ -93,7 +98,7 @@ public AudioClip getRegisteredSound(String name) { * @param eventId a string for the eventId * @param clip the audio clip for the sound */ - public void registerSoundEvent(String eventId, AudioClip clip) { + public static void registerSoundEvent(String eventId, AudioClip clip) { soundEventMap.put(eventId, clip); } @@ -102,7 +107,7 @@ public void registerSoundEvent(String eventId, AudioClip clip) { * * @param eventId a string for the eventId */ - public void registerSoundEvent(String eventId) { + public static void registerSoundEvent(String eventId) { registerSoundEvent(eventId, null); } @@ -111,7 +116,7 @@ public void registerSoundEvent(String eventId) { * * @param eventId a string for the eventId */ - public void playSoundEvent(String eventId) { + public static void playSoundEvent(String eventId) { AudioClip clip = soundEventMap.get(eventId); double volume = MediaPlayerAdapter.getGlobalVolume(); @@ -127,4 +132,115 @@ public void run() { }); } } + + /** + * Start a given clip from its url string. + * + * @param strUri the String url of the clip + * @param cycleCount how many times should the clip play. -1: infinite + * @param volume the volume level of the clip (0-1) + * @return false if the file doesn't exist, true otherwise + * @throws ParserException if issue with file + */ + public static boolean playClip(String strUri, int cycleCount, double volume) + throws ParserException { + if (!userSounds.containsKey(strUri)) { + try { + if (!MediaPlayerAdapter.uriExists(strUri)) + return false; // leave without error message if uri ok but no file + } catch (Exception e) { + throw new ParserException( + I18N.getText( + "macro.function.sound.illegalargument", + "playClip", + strUri, + e.getLocalizedMessage())); + } + } + + // run this on the JavaFX thread + Platform.runLater( + new Runnable() { + @Override + public void run() { + AudioClip clip = userSounds.get(strUri); + if (clip == null) { + clip = new AudioClip(strUri); + userSounds.put(strUri, clip); + } + double playVolume = volume * MediaPlayerAdapter.getGlobalVolume(); + if (cycleCount != 0 && playVolume > 0 && !MediaPlayerAdapter.getGlobalMute()) { + clip.setCycleCount(cycleCount); + clip.play(playVolume); + } + } + }); + return true; + } + + /** + * Stop a given clip from its url string. + * + * @param strUri the String url of the clip + * @param remove should the clip be disposed + */ + public static void stopClip(String strUri, boolean remove) { + Platform.runLater( + new Runnable() { + @Override + public void run() { + if (strUri.equals("*")) { + for (HashMap.Entry mapElement : userSounds.entrySet()) { + ((AudioClip) mapElement.getValue()).stop(); + } + if (remove) userSounds.clear(); + } else { + AudioClip clip = userSounds.get(strUri); + if (clip != null) clip.stop(); + if (remove) userSounds.remove(strUri); + } + } + }); + } + + /** + * Return the properties of a clip from its uri + * + * @param strUri the String uri of the clip + * @return JSONObject for one clip, JSONArray of JSONObjects if all clips + */ + public static Object getClipProperties(String strUri) { + JSONObject info; + if (strUri.equals("*")) { + JSONArray infoArray = new JSONArray(); + for (HashMap.Entry mapElement : userSounds.entrySet()) { + info = getInfo((String) mapElement.getKey()); + if (info != null) infoArray.add(info); + } + return infoArray; + } else { + info = getInfo(strUri); + if (info == null) return ""; + else return info; + } + } + + /** + * Return the properties of a clip + * + * @return JSONObject of the properties + */ + private static JSONObject getInfo(String strUri) { + AudioClip clip = userSounds.get(strUri); + if (clip == null) return null; + try { + JSONObject info = new JSONObject(); + info.put("uri", strUri); + String status = clip.isPlaying() ? "PLAYING" : "STOPPED"; + info.put("status", status); + return info; + } catch (Exception e) { + return null; + } + } } diff --git a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java index f8afefc40d..0706098403 100644 --- a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java @@ -16,6 +16,7 @@ import java.math.BigDecimal; import java.util.List; +import net.rptools.lib.sound.SoundManager; import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; @@ -28,7 +29,16 @@ public class SoundFunctions extends AbstractFunction { private static final SoundFunctions instance = new SoundFunctions(); private SoundFunctions() { - super(0, 5, "playStream", "stopStream", "editStream", "getStreamProperties"); + super( + 0, + 5, + "playStream", + "stopStream", + "editStream", + "getStreamProperties", + "playClip", + "stopClip", + "getClipProperties"); } /** @@ -87,6 +97,23 @@ public Object childEvaluate(Parser parser, String functionName, List arg FunctionUtil.checkNumberParam(functionName, args, 0, 1); String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; return MediaPlayerAdapter.getStreamProperties(strUri); + } else if (functionName.equalsIgnoreCase("playClip")) { + FunctionUtil.checkNumberParam(functionName, args, 1, 3); + if (!AppPreferences.getPlayStreams()) return -1; // do nothing if disabled in preferences + String strUri = MediaPlayerAdapter.convertToURI(args.get(0)); + int cycleCount = psize > 1 ? FunctionUtil.paramAsInteger(functionName, args, 1, true) : 1; + double volume = psize > 2 ? FunctionUtil.paramAsDouble(functionName, args, 2, true) : 1; + return SoundManager.playClip(strUri, cycleCount, volume) ? BigDecimal.ONE : BigDecimal.ZERO; + } else if (functionName.equalsIgnoreCase("stopClip")) { + FunctionUtil.checkNumberParam(functionName, args, 0, 2); + String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; + boolean del = psize > 1 ? FunctionUtil.paramAsBoolean(functionName, args, 1, true) : true; + SoundManager.stopClip(strUri, del); + return ""; + } else if (functionName.equalsIgnoreCase("getClipProperties")) { + FunctionUtil.checkNumberParam(functionName, args, 0, 1); + String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; + return SoundManager.getClipProperties(strUri); } return null; } From 8563d2fefa65cda6bb5828db91bf4e3225bee8b0 Mon Sep 17 00:00:00 2001 From: Merudo Date: Mon, 21 Oct 2019 06:27:25 -0500 Subject: [PATCH 2/2] Change stopStream/getStreamProperties to work with clips - Change names of stopStream and getStreamProperties to stopSound and getSoundProperties. The new functions work with both streams and clips. - Add function "defineAudioSource". You can now define a nickname for an audio file, with defineAudioSource(nickname, uri). - Add already included sounds Dink and Clink as defined audio sources. They can now be played with playClip("Dink") and playClip("Clink") - Resolve issues discussed in #810 --- .../net/rptools/lib/sound/SoundManager.java | 15 +- .../client/functions/MediaPlayerAdapter.java | 86 +------- .../client/functions/SoundFunctions.java | 193 +++++++++++++++--- 3 files changed, 184 insertions(+), 110 deletions(-) diff --git a/src/main/java/net/rptools/lib/sound/SoundManager.java b/src/main/java/net/rptools/lib/sound/SoundManager.java index 4792aba8d0..87ba1e5730 100644 --- a/src/main/java/net/rptools/lib/sound/SoundManager.java +++ b/src/main/java/net/rptools/lib/sound/SoundManager.java @@ -24,6 +24,7 @@ import javafx.application.Platform; import javafx.scene.media.AudioClip; import net.rptools.maptool.client.functions.MediaPlayerAdapter; +import net.rptools.maptool.client.functions.SoundFunctions; import net.rptools.maptool.language.I18N; import net.rptools.parser.ParserException; import net.sf.json.JSONArray; @@ -67,7 +68,8 @@ public static void configure(Properties properties) { } /** - * Register a system sound from a path. If path incorrect or null, remove sound. + * Register a system sound from a path. If path incorrect or null, remove sound. Also add define + * the sound to be used in SoundFunctions. * * @param name the name of the sound * @param path the path to the sound @@ -78,8 +80,12 @@ public static void registerSound(String name, String path) { URL url = path != null ? SoundManager.class.getClassLoader().getResource(path) : null; AudioClip clip = url != null ? new AudioClip(url.toExternalForm()) : null; - if (clip != null) registeredSoundMap.put(name, clip); - else registeredSoundMap.remove(name); + if (clip != null) { + registeredSoundMap.put(name, clip); + SoundFunctions.defineSound(name, url.toExternalForm()); // add sound with defineAudioSource + } else { + registeredSoundMap.remove(name); + } } /** @@ -146,7 +152,7 @@ public static boolean playClip(String strUri, int cycleCount, double volume) throws ParserException { if (!userSounds.containsKey(strUri)) { try { - if (!MediaPlayerAdapter.uriExists(strUri)) + if (!SoundFunctions.uriExists(strUri)) return false; // leave without error message if uri ok but no file } catch (Exception e) { throw new ParserException( @@ -238,6 +244,7 @@ private static JSONObject getInfo(String strUri) { info.put("uri", strUri); String status = clip.isPlaying() ? "PLAYING" : "STOPPED"; info.put("status", status); + info.put("type", "clip"); return info; } catch (Exception e) { return null; diff --git a/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java b/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java index 70dbcae6e0..d7267eecd1 100644 --- a/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java +++ b/src/main/java/net/rptools/maptool/client/functions/MediaPlayerAdapter.java @@ -14,8 +14,6 @@ */ package net.rptools.maptool.client.functions; -import java.io.File; -import java.io.IOException; import java.net.*; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; @@ -81,7 +79,8 @@ public static boolean playStream( throws ParserException { final Media media; try { - if (!uriExists(strUri)) return false; // leave without error message if uri ok but no file + if (!SoundFunctions.uriExists(strUri)) + return false; // leave without error message if uri ok but no file media = new Media(strUri); } catch (Exception e) { throw new ParserException( @@ -236,54 +235,6 @@ private void editStream(Integer cycleCount, Double volume, Double start, Double } } - /** - * Return the existence status of resource from String uri - * - * @param strUri the String uri of the resource - * @return true if resource exists, false otherwise - * @throws IOException if uri is url, but url is incorrect - * @throws URISyntaxException if uri is for local file, but uri is incorrect - */ - public static boolean uriExists(String strUri) throws IOException, URISyntaxException { - return isWeb(strUri) ? urlExist(strUri) : fileExist(strUri); - } - - /** - * Returns true if the uri is for a web resource, false otherwise - * - * @param strUri the String uri of the resource - * @return true if String uri is URL, false otherwise - */ - private static boolean isWeb(String strUri) { - String s = strUri.trim().toLowerCase(); - return s.startsWith("http://") || s.startsWith("https://"); - } - - /** - * Return the existence status of web resource from String uri - * - * @param strUri the String uri of the resource - * @return true if resource exists, false otherwise - * @throws IOException if uri is incorrect - */ - private static boolean urlExist(String strUri) throws IOException { - HttpURLConnection.setFollowRedirects(false); - HttpURLConnection con = (HttpURLConnection) new URL(strUri).openConnection(); - con.setRequestMethod("HEAD"); - return (con.getResponseCode() == HttpURLConnection.HTTP_OK); - } - - /** - * Return the existence status of local resource from String uri - * - * @param strUri the String uri of the resource - * @return true if resource exists, false otherwise - * @throws URISyntaxException if uri is incorrect - */ - private static boolean fileExist(String strUri) throws URISyntaxException { - return new File(new URI(strUri).getPath()).exists(); - } - /** * Return the properties of a stream from its uri * @@ -339,6 +290,7 @@ private JSONObject getInfo() { info.put("bufferTime", player.getBufferProgressTime().toSeconds()); info.put("currentCount", player.getCurrentCount()); info.put("status", player.getStatus().toString()); + info.put("type", "stream"); return info; } catch (Exception e) { return null; @@ -406,36 +358,4 @@ private void updateMute() { public static boolean getGlobalMute() { return globalMute; } - - /** - * Convert a string into a uri string. Spaces are replaced by %20, among other things. The string - * "*" is returned as-is - * - * @param string the string to convert - * @return the converted string - */ - public static String convertToURI(Object string) { - String strUri = string.toString().trim(); - if (strUri.equals("*")) return strUri; - if (!isWeb(strUri) && !strUri.toUpperCase().startsWith("FILE")) { - strUri = "FILE:/" + strUri; - } - - try { - String decodedURL = URLDecoder.decode(strUri, "UTF-8"); - URL url = new URL(decodedURL); - URI uri = - new URI( - url.getProtocol(), - url.getUserInfo(), - url.getHost(), - url.getPort(), - url.getPath(), - url.getQuery(), - url.getRef()); - return uri.toString(); - } catch (Exception ex) { - return strUri; - } - } } diff --git a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java index 0706098403..2519389eff 100644 --- a/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java +++ b/src/main/java/net/rptools/maptool/client/functions/SoundFunctions.java @@ -14,14 +14,20 @@ */ package net.rptools.maptool.client.functions; +import java.io.File; +import java.io.IOException; import java.math.BigDecimal; +import java.net.*; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import net.rptools.lib.sound.SoundManager; import net.rptools.maptool.client.AppPreferences; import net.rptools.maptool.util.FunctionUtil; import net.rptools.parser.Parser; import net.rptools.parser.ParserException; import net.rptools.parser.function.AbstractFunction; +import net.sf.json.JSONArray; public class SoundFunctions extends AbstractFunction { @@ -33,14 +39,15 @@ private SoundFunctions() { 0, 5, "playStream", - "stopStream", - "editStream", - "getStreamProperties", "playClip", - "stopClip", - "getClipProperties"); + "stopSound", + "getSoundProperties", + "editStream", + "defineAudioSource"); } + private static final ConcurrentHashMap mapSounds = new ConcurrentHashMap<>(); + /** * Gets the SoundFunctions instance. * @@ -57,7 +64,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg if (functionName.equalsIgnoreCase("playStream")) { FunctionUtil.checkNumberParam(functionName, args, 1, 5); if (!AppPreferences.getPlayStreams()) return -1; // do nothing if disabled in preferences - String strUri = MediaPlayerAdapter.convertToURI(args.get(0)); + String strUri = convertToURI(args.get(0), true); int cycleCount = psize > 1 ? FunctionUtil.paramAsInteger(functionName, args, 1, true) : 1; double volume = psize > 2 ? FunctionUtil.paramAsDouble(functionName, args, 2, true) : 1; double start = psize > 3 ? FunctionUtil.paramAsDouble(functionName, args, 3, true) * 1000 : 0; @@ -68,7 +75,7 @@ public Object childEvaluate(Parser parser, String functionName, List arg : BigDecimal.ZERO; } else if (functionName.equalsIgnoreCase("editStream")) { FunctionUtil.checkNumberParam(functionName, args, 2, 5); - String strUri = MediaPlayerAdapter.convertToURI(args.get(0)); + String strUri = convertToURI(args.get(0), true); Integer cycleCount = null; Double volume = null; @@ -86,35 +93,175 @@ public Object childEvaluate(Parser parser, String functionName, List arg MediaPlayerAdapter.editStream(strUri, cycleCount, volume, start, stop); return ""; - } else if (functionName.equalsIgnoreCase("stopStream")) { + } else if (functionName.equalsIgnoreCase("stopSound")) { FunctionUtil.checkNumberParam(functionName, args, 0, 3); - String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; + String strUri = psize > 0 ? convertToURI(args.get(0), true) : "*"; boolean del = psize > 1 ? FunctionUtil.paramAsBoolean(functionName, args, 1, true) : true; double fade = psize > 2 ? FunctionUtil.paramAsDouble(functionName, args, 2, true) * 1000 : 0; - MediaPlayerAdapter.stopStream(strUri, del, fade); + stopSound(strUri, del, fade); // stop clip and/or stream return ""; - } else if (functionName.equalsIgnoreCase("getStreamProperties")) { + } else if (functionName.equalsIgnoreCase("getSoundProperties")) { FunctionUtil.checkNumberParam(functionName, args, 0, 1); - String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; - return MediaPlayerAdapter.getStreamProperties(strUri); + String strUri = psize > 0 ? convertToURI(args.get(0), true) : "*"; + return getSoundProperties(strUri); } else if (functionName.equalsIgnoreCase("playClip")) { FunctionUtil.checkNumberParam(functionName, args, 1, 3); if (!AppPreferences.getPlayStreams()) return -1; // do nothing if disabled in preferences - String strUri = MediaPlayerAdapter.convertToURI(args.get(0)); + String strUri = convertToURI(args.get(0), true); int cycleCount = psize > 1 ? FunctionUtil.paramAsInteger(functionName, args, 1, true) : 1; double volume = psize > 2 ? FunctionUtil.paramAsDouble(functionName, args, 2, true) : 1; return SoundManager.playClip(strUri, cycleCount, volume) ? BigDecimal.ONE : BigDecimal.ZERO; - } else if (functionName.equalsIgnoreCase("stopClip")) { - FunctionUtil.checkNumberParam(functionName, args, 0, 2); - String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; - boolean del = psize > 1 ? FunctionUtil.paramAsBoolean(functionName, args, 1, true) : true; - SoundManager.stopClip(strUri, del); + } else if (functionName.equalsIgnoreCase("defineAudioSource")) { + FunctionUtil.checkNumberParam(functionName, args, 2, 2); + String nickName = args.get(0).toString(); + String strUri = convertToURI(args.get(1), false); + defineSound(nickName, strUri); return ""; - } else if (functionName.equalsIgnoreCase("getClipProperties")) { - FunctionUtil.checkNumberParam(functionName, args, 0, 1); - String strUri = psize > 0 ? MediaPlayerAdapter.convertToURI(args.get(0)) : "*"; - return SoundManager.getClipProperties(strUri); } return null; } + + /** + * Give a sound URI a nickname for easier access. + * + * @param nickName the nickname to use for the audio + * @param strUri the String uri of the stream or clip + */ + public static void defineSound(String nickName, String strUri) { + mapSounds.put(nickName, strUri); + } + + /** + * Get a JSON containing the properties of the sound(s) + * + * @param strUri the String uri of the stream or clip. Can also be "*", "streams", or "clips". + */ + private static Object getSoundProperties(String strUri) { + switch (strUri.toLowerCase()) { + case "streams": + return MediaPlayerAdapter.getStreamProperties("*"); + case "clips": + return SoundManager.getClipProperties("*"); + case "*": + JSONArray all = (JSONArray) MediaPlayerAdapter.getStreamProperties("*"); + all.addAll((JSONArray) SoundManager.getClipProperties("*")); + return all; + default: + Object streams = MediaPlayerAdapter.getStreamProperties(strUri); + if (!"".equals(streams)) { + return streams; + } else { + return SoundManager.getClipProperties(strUri); + } + } + } + + /** + * Stop a sound, either a stream, clip, or both + * + * @param strUri the String uri of the stream or clip. Can also be "*", "streams", or "clips". + */ + private static void stopSound(String strUri, boolean remove, double fadeout) { + switch (strUri.toLowerCase()) { + case "streams": + MediaPlayerAdapter.stopStream("*", remove, fadeout); + break; + case "clips": + SoundManager.stopClip("*", remove); + break; + default: + MediaPlayerAdapter.stopStream(strUri, remove, fadeout); + SoundManager.stopClip(strUri, remove); + break; + } + } + + /** + * Convert a string into a uri string. Spaces are replaced by %20, among other things. The string + * "*" is returned as-is + * + * @param string the string to convert + * @param checkMapSounds should we check mapSounds for a defined sound? + * @return the converted string + */ + private static String convertToURI(Object string, boolean checkMapSounds) { + String strUri = string.toString().trim(); + if (strUri.equals("*") + || strUri.equalsIgnoreCase("clips") + || strUri.equalsIgnoreCase("streams")) { + return strUri; + } + if (checkMapSounds && mapSounds.containsKey(strUri)) { + return mapSounds.get(strUri); + } + + if (!isWeb(strUri) && !strUri.toUpperCase().startsWith("FILE")) { + strUri = "FILE:/" + strUri; + } + + try { + String decodedURL = URLDecoder.decode(strUri, StandardCharsets.UTF_8); + URL url = new URL(decodedURL); + URI uri = + new URI( + url.getProtocol(), + url.getUserInfo(), + url.getHost(), + url.getPort(), + url.getPath(), + url.getQuery(), + url.getRef()); + return uri.toString(); + } catch (Exception ex) { + return strUri; + } + } + + /** + * Return the existence status of resource from String uri + * + * @param strUri the String uri of the resource + * @return true if resource exists, false otherwise + * @throws IOException if uri is url, but url is incorrect + * @throws URISyntaxException if uri is for local file, but uri is incorrect + */ + public static boolean uriExists(String strUri) throws IOException, URISyntaxException { + return isWeb(strUri) ? urlExist(strUri) : fileExist(strUri); + } + + /** + * Returns true if the uri is for a web resource, false otherwise + * + * @param strUri the String uri of the resource + * @return true if String uri is URL, false otherwise + */ + private static boolean isWeb(String strUri) { + String s = strUri.trim().toLowerCase(); + return s.startsWith("http://") || s.startsWith("https://"); + } + + /** + * Return the existence status of web resource from String uri + * + * @param strUri the String uri of the resource + * @return true if resource exists, false otherwise + * @throws IOException if uri is incorrect + */ + private static boolean urlExist(String strUri) throws IOException { + HttpURLConnection.setFollowRedirects(false); + HttpURLConnection con = (HttpURLConnection) new URL(strUri).openConnection(); + con.setRequestMethod("HEAD"); + return (con.getResponseCode() == HttpURLConnection.HTTP_OK); + } + + /** + * Return the existence status of local resource from String uri + * + * @param strUri the String uri of the resource + * @return true if resource exists, false otherwise + * @throws URISyntaxException if uri is incorrect + */ + private static boolean fileExist(String strUri) throws URISyntaxException { + return new File(new URI(strUri).getPath()).exists(); + } }