/*
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package com.sun.javafx.css.converters;
import com.sun.javafx.css.
StyleConverterImpl;
import javafx.application.
Application;
import javafx.css.
ParsedValue;
import javafx.css.
StyleConverter;
import javafx.scene.text.
Font;
import sun.util.logging.
PlatformLogger;
import java.net.
MalformedURLException;
import java.net.
URI;
import java.net.
URISyntaxException;
import java.net.
URL;
import java.security.
AccessController;
import java.security.
CodeSource;
import java.security.
PrivilegedActionException;
import java.security.
PrivilegedExceptionAction;
import java.security.
ProtectionDomain;
/**
* Convert url("<path>") a URL string resolved relative to the location of the stylesheet.
*/
public final class
URLConverter extends
StyleConverterImpl<
ParsedValue[],
String> {
// lazy, thread-safe instatiation
private static class
Holder {
static final
URLConverter INSTANCE = new
URLConverter();
static final
SequenceConverter SEQUENCE_INSTANCE = new
SequenceConverter();
}
public static
StyleConverter<
ParsedValue[],
String>
getInstance() {
return
Holder.
INSTANCE;
}
private
URLConverter() {
super();
}
@
Override
public
String convert(
ParsedValue<
ParsedValue[],
String>
value,
Font font) {
String url = null;
ParsedValue[]
values =
value.
getValue();
String resource =
values.length > 0 ?
StringConverter.
getInstance().
convert(
values[0],
font) : null;
if (
resource != null &&
resource.
trim().
isEmpty() == false) {
if (
resource.
startsWith("url(")) {
resource = com.sun.javafx.util.
Utils.
stripQuotes(
resource.
substring(4,
resource.
length() - 1));
} else {
resource = com.sun.javafx.util.
Utils.
stripQuotes(
resource);
}
String stylesheetURL =
values.length > 1 &&
values[1] != null ? (
String)
values[1].
getValue() : null;
URL resolvedURL =
resolve(
stylesheetURL,
resource);
if (
resolvedURL != null)
url =
resolvedURL.
toExternalForm();
}
return
url;
}
// package for testing
URL resolve(
String stylesheetUrl,
String resource) {
final
String resourcePath = (
resource != null) ?
resource.
trim() : null;
if (
resourcePath == null ||
resourcePath.
isEmpty()) return null;
try {
// Note: the same code (pretty much) also appears in StyleManager
// if stylesheetUri is null, then we're dealing with an in-line style.
// If there is no scheme part, then the url is interpreted as being relative to the application's class-loader.
URI resourceUri = new
URI(
resourcePath);
if (
resourceUri.
isAbsolute()) {
return
resourceUri.
toURL();
}
URL rtJarUrl =
resolveRuntimeImport(
resourceUri);
if (
rtJarUrl != null) {
return
rtJarUrl;
}
final
String path =
resourceUri.
getPath();
if (
path.
startsWith("/")) {
final
ClassLoader contextClassLoader =
Thread.
currentThread().
getContextClassLoader();
return
contextClassLoader.
getResource(
path.
substring(1));
}
final
String stylesheetPath = (
stylesheetUrl != null) ?
stylesheetUrl.
trim() : null;
if (
stylesheetPath != null &&
stylesheetPath.
isEmpty() == false) {
URI stylesheetUri = new
URI(
stylesheetPath);
if (
stylesheetUri.
isOpaque() == false) {
URI resolved =
stylesheetUri.
resolve(
resourceUri);
return
resolved.
toURL();
} else {
// stylesheet URI is something like jar:file:
URL url =
stylesheetUri.
toURL();
return new
URL(
url,
resourceUri.
getPath());
}
}
// URL doesn't have scheme or stylesheetUrl is null
final
ClassLoader contextClassLoader =
Thread.
currentThread().
getContextClassLoader();
return
contextClassLoader.
getResource(
path);
} catch (final
MalformedURLException|
URISyntaxException e) {
PlatformLogger cssLogger = com.sun.javafx.util.
Logging.
getCSSLogger();
if (
cssLogger.
isLoggable(
PlatformLogger.
Level.
WARNING)) {
cssLogger.
warning(
e.
getLocalizedMessage());
}
return null;
}
}
//
// Resolve a path from an @import that implies jfxrt.jar,
// e.g., @import "com/sun/javafx/scene/control/skin/modena/modena.css".
//
// See also StyleSheet#loadStylesheet(String)
//
private
URL resolveRuntimeImport(final
URI resourceUri) {
final
String path =
resourceUri.
getPath();
final
String resourcePath =
path.
startsWith("/") ?
path.
substring(1) :
path;
if ((
resourcePath.
startsWith("com/sun/javafx/scene/control/skin/modena/") ||
resourcePath.
startsWith("com/sun/javafx/scene/control/skin/caspian/")) &&
(
resourcePath.
endsWith(".css") ||
resourcePath.
endsWith(".bss"))) {
final
SecurityManager sm =
System.
getSecurityManager();
if (
sm == null) {
// If the SecurityManager is not null, then just look up the resource on the class-path.
// If there is a SecurityManager, the URLClassPath getResource call will return null,
// so fall through and create a URL from the code-source URI
final
ClassLoader contextClassLoader =
Thread.
currentThread().
getContextClassLoader();
final
URL resolved =
contextClassLoader.
getResource(
resourcePath);
return
resolved;
}
// check whether the path is file from our runtime jar
try {
final
URL rtJarURL =
AccessController.
doPrivileged((
PrivilegedExceptionAction<
URL>) () -> {
// getProtectionDomain either throws a SecurityException or returns a non-null value
final
ProtectionDomain protectionDomain =
Application.class.
getProtectionDomain();
// If we're running with a SecurityManager, then the ProtectionDomain will have a CodeSource
final
CodeSource codeSource =
protectionDomain.
getCodeSource();
// The CodeSource location will be our runtime jar
return
codeSource.
getLocation();
});
final
URI rtJarURI =
rtJarURL.
toURI();
String scheme =
rtJarURI.
getScheme();
String rtJarPath =
rtJarURI.
getPath();
//
// Just because we're running with a SecurityManager doesn't mean the jfxrt jar path is
// a jar: URL. But the code in StyleManager wants it to be. So if we have
// file:/blah/rt/lib/ext/jfxrt.jar make it jar:file:/blah/rt/lib/ext/jfxrt.jar!/
//
// If the path doesn't end with .jar, then we are just dealing with a normal file: path
//
if ("file".
equals(
scheme) &&
rtJarPath.
endsWith(".jar")) {
if ("file".
equals(
scheme)) {
scheme = "jar:file";
rtJarPath =
rtJarPath.
concat("!/");
}
}
rtJarPath =
rtJarPath.
concat(
resourcePath);
final
String rtJarUserInfo =
rtJarURI.
getUserInfo();
final
String rtJarHost =
rtJarURI.
getHost();
final int
rtJarPort =
rtJarURI.
getPort();
//
// Put together a new URI from the pieces of rtJarURI. We cannot use resolve here since
// the scheme and path may have been munged.
//
URI resolved = new
URI(
scheme,
rtJarUserInfo,
rtJarHost,
rtJarPort,
rtJarPath, null, null);
return
resolved.
toURL();
} catch (
URISyntaxException |
MalformedURLException |
PrivilegedActionException ignored) {
// Allow this method to return null so the caller will try to further resolve the path.
// If nothing else, an error message will result when the converted URL is consumed.
}
}
return null;
}
@
Override
public
String toString() {
return "URLType";
}
public static final class
SequenceConverter extends
StyleConverterImpl<
ParsedValue<
ParsedValue[],
String>[],
String[]> {
public static
SequenceConverter getInstance() {
return
Holder.
SEQUENCE_INSTANCE;
}
private
SequenceConverter() {
super();
}
@
Override
public
String[]
convert(
ParsedValue<
ParsedValue<
ParsedValue[],
String>[],
String[]>
value,
Font font) {
ParsedValue<
ParsedValue[],
String>[]
layers =
value.
getValue();
String[]
urls = new
String[
layers.length];
for (int
layer = 0;
layer <
layers.length;
layer++) {
urls[
layer] =
URLConverter.
getInstance().
convert(
layers[
layer],
font);
}
return
urls;
}
@
Override
public
String toString() {
return "URLSeqType";
}
}
}