/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package com.sun.javafx.css;
import javafx.css.
ParsedValue;
import javafx.css.
StyleConverter;
import javafx.scene.paint.
Color;
import javafx.scene.text.
Font;
import java.io.
DataInputStream;
import java.io.
DataOutputStream;
import java.io.
IOException;
import java.net.
MalformedURLException;
import java.net.
URL;
/**
* Implementation details behind a {@link ParsedValueImpl}.
*/
public class
ParsedValueImpl<V, T> extends
ParsedValue<V,T> {
/**
* If value references another property, then the real value needs to
* be looked up.
*/
final private boolean
lookup;
public final boolean
isLookup() { return
lookup; }
/**
* If value is itself a ParsedValueImpl or sequence of values, and should any of
* those values need to be looked up, then this flag is set. This
* does not mean that this particular value needs to be looked up, but
* that this value contains a value that needs to be looked up.
*/
final private boolean
containsLookups;
public final boolean
isContainsLookups() { return
containsLookups; }
private static boolean
getContainsLookupsFlag(
Object obj) {
// Assume the value does not contain lookups
boolean
containsLookupsFlag = false;
if (
obj instanceof
Size) {
containsLookupsFlag = false;
}
else if(
obj instanceof
ParsedValueImpl) {
ParsedValueImpl value = (
ParsedValueImpl)
obj;
containsLookupsFlag =
value.
lookup ||
value.
containsLookups;
}
else if(
obj instanceof
ParsedValueImpl[]) {
ParsedValueImpl[]
values = (
ParsedValueImpl[])
obj;
for(int
v=0;
// Bail if value contains lookups
// Continue iterating as long as one of the flags is false
v<
values.length && !
containsLookupsFlag;
v++)
{
if (
values[
v] != null) {
containsLookupsFlag =
containsLookupsFlag
||
values[
v].
lookup
||
values[
v].
containsLookups;
}
}
} else if(
obj instanceof
ParsedValueImpl[][]) {
ParsedValueImpl[][]
values = (
ParsedValueImpl[][])
obj;
for(int
l=0;
l<
values.length && !
containsLookupsFlag;
l++)
{
if (
values[
l] != null) {
for(int
v=0;
v<
values[
l].length && !
containsLookupsFlag;
v++)
{
if (
values[
l][
v] != null) {
containsLookupsFlag =
containsLookupsFlag
||
values[
l][
v].
lookup
||
values[
l][
v].
containsLookups;
}
}
}
}
}
return
containsLookupsFlag;
}
public static boolean
containsFontRelativeSize(
ParsedValue parsedValue, boolean
percentUnitsAreRelative) {
// Assume the value does not need a font for conversion
boolean
needsFont = false;
Object obj =
parsedValue.
getValue();
if (
obj instanceof
Size) {
Size size = (
Size)
obj;
// percent is only relative for font and font-size properties
needsFont =
size.
getUnits() ==
SizeUnits.
PERCENT
?
percentUnitsAreRelative
:
size.
isAbsolute() == false;
}
else if(
obj instanceof
ParsedValue) {
ParsedValue value = (
ParsedValueImpl)
obj;
needsFont =
containsFontRelativeSize(
value,
percentUnitsAreRelative);
}
else if(
obj instanceof
ParsedValue[]) {
ParsedValue[]
values = (
ParsedValue[])
obj;
for(int
v=0;
v<
values.length && !
needsFont;
v++)
{
if (
values[
v] == null) continue;
needsFont =
containsFontRelativeSize(
values[
v],
percentUnitsAreRelative);
}
} else if(
obj instanceof
ParsedValueImpl[][]) {
ParsedValueImpl[][]
values = (
ParsedValueImpl[][])
obj;
for(int
l=0;
l<
values.length && !
needsFont;
l++)
{
if (
values[
l] == null) continue;
for(int
v=0;
v<
values[
l].length && !
needsFont;
v++)
{
if (
values[
l][
v] == null) continue;
needsFont =
containsFontRelativeSize(
values[
l][
v],
percentUnitsAreRelative);
}
}
}
return
needsFont;
}
/**
* Create an instance of ParsedValueImpl where the value type V is converted to
* the target type T using the given Type converter. If the value needs
* If type is null, then it is assumed that the value type V and the target
* type T are the same (do not need converted). If lookup is true, then
* the value is another property.
*/
public
ParsedValueImpl(V
value,
StyleConverter<V, T>
converter, boolean
lookup) {
super(
value,
converter);
this.
lookup =
lookup;
this.
containsLookups =
lookup ||
getContainsLookupsFlag(
value);
}
/**
* Create an instance of ParsedValueImpl where the value type V is converted to
* the target type T using the given Type converter. If the value needs
* If type is null, then it is assumed that the value type V and the target
* type T are the same (do not need converted).
*/
public
ParsedValueImpl(V
value,
StyleConverter<V, T>
type) {
this(
value,
type, false);
}
public T
convert(
Font font) {
return (T)((
converter != null) ?
converter.
convert(this,
font) :
value);
}
private static int
indent = 0;
private static
String spaces() {
return new
String(new char[
indent]).
replace('\0', ' ');
}
private static void
indent() {
indent += 2;
}
private static void
outdent() {
indent =
Math.
max(0,
indent-2);
}
@
Override public
String toString() {
final
String newline =
System.
lineSeparator();
StringBuilder sbuf = new
StringBuilder();
sbuf.
append(
spaces())
.
append((
lookup? "<Value lookup=\"true\">" : "<Value>"))
.
append(
newline);
indent();
if (
value != null) {
appendValue(
sbuf,
value, "value");
} else {
appendValue(
sbuf, "null", "value");
}
sbuf.
append(
spaces())
.
append("<converter>")
.
append(
converter)
.
append("</converter>")
.
append(
newline);
outdent();
sbuf.
append(
spaces()).
append("</Value>");
return
sbuf.
toString();
}
private void
appendValue(
StringBuilder sbuf,
Object value,
String tag) {
final
String newline =
System.
lineSeparator();
if (
value instanceof
ParsedValueImpl[][]) {
ParsedValueImpl[][]
layers = (
ParsedValueImpl[][])
value;
sbuf.
append(
spaces())
.
append('<')
.
append(
tag)
.
append(" layers=\"")
.
append(
layers.length)
.
append("\">")
.
append(
newline);
indent();
for (
ParsedValueImpl[]
layer :
layers) {
sbuf.
append(
spaces())
.
append("<layer>")
.
append(
newline);
indent();
if (
layer == null) {
sbuf.
append(
spaces()).
append("null").
append(
newline);
continue;
}
for(
ParsedValueImpl val :
layer) {
if (
val == null) {
sbuf.
append(
spaces()).
append("null").
append(
newline);
} else {
sbuf.
append(
val);
}
}
outdent();
sbuf.
append(
spaces())
.
append("</layer>")
.
append(
newline);
}
outdent();
sbuf.
append(
spaces()).
append("</").
append(
tag).
append('>').
append(
newline);
} else if (
value instanceof
ParsedValueImpl[]) {
ParsedValueImpl[]
values = (
ParsedValueImpl[])
value;
sbuf.
append(
spaces())
.
append('<')
.
append(
tag)
.
append(" values=\"")
.
append(
values.length)
.
append("\">")
.
append(
newline);
indent();
for(
ParsedValueImpl val :
values) {
if (
val == null) {
sbuf.
append(
spaces()).
append("null").
append(
newline);
} else {
sbuf.
append(
val);
}
}
outdent();
sbuf.
append(
spaces()).
append("</").
append(
tag).
append('>').
append(
newline);
} else if (
value instanceof
ParsedValueImpl) {
sbuf.
append(
spaces()).
append('<').
append(
tag).
append('>').
append(
newline);
indent();
sbuf.
append(
value);
outdent();
sbuf.
append(
spaces()).
append("</").
append(
tag).
append('>').
append(
newline);
} else {
sbuf.
append(
spaces()).
append('<').
append(
tag).
append('>');
sbuf.
append(
value);
sbuf.
append("</").
append(
tag).
append('>').
append(
newline);
}
}
@
Override public boolean
equals(
Object obj) {
if (
obj == this) return true;
if (
obj == null ||
obj.
getClass() != this.
getClass()) {
return false;
}
final
ParsedValueImpl other = (
ParsedValueImpl)
obj;
if (this.
hash !=
other.
hash) return false;
if (this.
value instanceof
ParsedValueImpl[][]) {
if (!(
other.
value instanceof
ParsedValueImpl[][])) return false;
final
ParsedValueImpl[][]
thisValues = (
ParsedValueImpl[][])this.
value;
final
ParsedValueImpl[][]
otherValues = (
ParsedValueImpl[][])
other.
value;
// this.value and other.value are known to be non-null
// due to instanceof
if (
thisValues.length !=
otherValues.length) return false;
for (int
i = 0;
i <
thisValues.length;
i++) {
// if thisValues[i] is null, then otherValues[i] must be null
// if thisValues[i] is not null, then otherValues[i] must
// not be null
if ((
thisValues[
i] == null) && (
otherValues[
i] == null)) continue;
else if ((
thisValues[
i] == null) || (
otherValues[
i] == null)) return false;
if (
thisValues[
i].length !=
otherValues[
i].length) return false;
for (int
j = 0;
j <
thisValues[
i].length;
j++) {
final
ParsedValueImpl thisValue =
thisValues[
i][
j];
final
ParsedValueImpl otherValue =
otherValues[
i][
j];
if (
thisValue != null
? !
thisValue.
equals(
otherValue)
:
otherValue != null)
return false;
}
}
return true;
} else if (this.
value instanceof
ParsedValueImpl[]) {
if (!(
other.
value instanceof
ParsedValueImpl[])) return false;
final
ParsedValueImpl[]
thisValues = (
ParsedValueImpl[])this.
value;
final
ParsedValueImpl[]
otherValues = (
ParsedValueImpl[])
other.
value;
// this.value and other.value are known to be non-null
// due to instanceof
if (
thisValues.length !=
otherValues.length) return false;
for (int
i = 0;
i <
thisValues.length;
i++) {
final
ParsedValueImpl thisValue =
thisValues[
i];
final
ParsedValueImpl otherValue =
otherValues[
i];
if ((
thisValue != null)
? !
thisValue.
equals(
otherValue)
:
otherValue != null)
return false;
}
return true;
} else {
// RT-24614 - "CENTER" should equal "center"
if (this.
value instanceof
String &&
other.
value instanceof
String) {
return this.
value.
toString().
equalsIgnoreCase(
other.
value.
toString());
}
return (this.
value != null
? this.
value.
equals(
other.
value)
:
other.
value == null);
}
// Converter could be null, but the values could still match.
// It makes sense that ParsedValueImpl<String,String>("abc", null) should equal
// ParsedValueImpl<String,String>("abc", StringConverter.getInstance())
// (converter == null ? other.converter == null : converter.equals(other.converter));
}
private int
hash =
Integer.
MIN_VALUE;
@
Override public int
hashCode() {
if (
hash ==
Integer.
MIN_VALUE) {
hash = 17;
if (
value instanceof
ParsedValueImpl[][]) {
ParsedValueImpl[][]
values = (
ParsedValueImpl[][])
value;
for (int
i = 0;
i <
values.length;
i++) {
for (int
j = 0;
j <
values[
i].length;
j++) {
final
ParsedValueImpl val =
values[
i][
j];
hash = 37 *
hash + ((
val != null &&
val.
value != null) ?
val.
value.
hashCode() : 0);
}
}
} else if (
value instanceof
ParsedValueImpl[]) {
ParsedValueImpl[]
values = (
ParsedValueImpl[])
value;
for (int
i = 0;
i <
values.length;
i++) {
if (
values[
i] == null ||
values[
i].
value == null) continue;
final
ParsedValueImpl val =
values[
i];
hash = 37 *
hash + ((
val != null &&
val.
value != null) ?
val.
value.
hashCode() : 0);
}
} else {
hash = 37 *
hash + (
value != null ?
value.
hashCode() : 0);
}
// Converter could be null, but the values could still match.
// It makes sense that ParsedValueImpl<String,String>("abc", null) should equal
// ParsedValueImpl<String,String>("abc", StringConverter.getInstance())
// hash = 37 * hash + ((converter != null) ? converter.hashCode() : 1237);
}
return
hash;
}
final static private byte
NULL_VALUE = 0;
final static private byte
VALUE = 1;
final static private byte
VALUE_ARRAY = 2;
final static private byte
ARRAY_OF_VALUE_ARRAY = 3;
final static private byte
STRING = 4;
final static private byte
COLOR = 5;
final static private byte
ENUM = 6;
final static private byte
BOOLEAN = 7;
final static private byte
URL = 8;
final static private byte
SIZE = 9;
public final void
writeBinary(
DataOutputStream os,
StringStore stringStore)
throws
IOException {
os.
writeBoolean(
lookup);
if (
converter instanceof
StyleConverterImpl) {
os.
writeBoolean(true);
((
StyleConverterImpl)
converter).
writeBinary(
os,
stringStore);
} else {
os.
writeBoolean(false);
if (
converter != null) {
System.
err.
println("cannot writeBinary " +
converter.
getClass().
getName());
}
}
if (
value instanceof
ParsedValue) {
os.
writeByte(
VALUE);
final
ParsedValue pv = (
ParsedValue)
value;
if (
pv instanceof
ParsedValueImpl) {
((
ParsedValueImpl)
pv).
writeBinary(
os,
stringStore);
} else {
final
ParsedValueImpl impl = new
ParsedValueImpl(
pv.
getValue(),
pv.
getConverter());
impl.
writeBinary(
os,
stringStore);
}
} else if (
value instanceof
ParsedValue[]) {
os.
writeByte(
VALUE_ARRAY);
final
ParsedValue[]
values = (
ParsedValue[])
value;
if (
values != null) {
os.
writeByte(
VALUE);
} else {
os.
writeByte(
NULL_VALUE);
}
final int
nValues = (
values != null) ?
values.length : 0;
os.
writeInt(
nValues);
for (int
v=0;
v<
nValues;
v++) {
if (
values[
v] != null) {
os.
writeByte(
VALUE);
final
ParsedValue pv =
values[
v];
if (
pv instanceof
ParsedValueImpl) {
((
ParsedValueImpl)
pv).
writeBinary(
os,
stringStore);
} else {
final
ParsedValueImpl impl = new
ParsedValueImpl(
pv.
getValue(),
pv.
getConverter());
impl.
writeBinary(
os,
stringStore);
}
} else {
os.
writeByte(
NULL_VALUE);
}
}
} else if (
value instanceof
ParsedValue[][]) {
os.
writeByte(
ARRAY_OF_VALUE_ARRAY);
final
ParsedValue[][]
layers = (
ParsedValue[][])
value;
if (
layers != null) {
os.
writeByte(
VALUE);
} else {
os.
writeByte(
NULL_VALUE);
}
final int
nLayers = (
layers != null) ?
layers.length : 0;
os.
writeInt(
nLayers);
for (int
l=0;
l<
nLayers;
l++) {
final
ParsedValue[]
values =
layers[
l];
if (
values != null) {
os.
writeByte(
VALUE);
} else {
os.
writeByte(
NULL_VALUE);
}
final int
nValues = (
values != null) ?
values.length : 0;
os.
writeInt(
nValues);
for (int
v=0;
v<
nValues;
v++) {
if (
values[
v] != null) {
os.
writeByte(
VALUE);
final
ParsedValue pv =
values[
v];
if (
pv instanceof
ParsedValueImpl) {
((
ParsedValueImpl)
pv).
writeBinary(
os,
stringStore);
} else {
final
ParsedValueImpl impl = new
ParsedValueImpl(
pv.
getValue(),
pv.
getConverter());
impl.
writeBinary(
os,
stringStore);
}
} else {
os.
writeByte(
NULL_VALUE);
}
}
}
} else if (
value instanceof
Color) {
final
Color c = (
Color)
value;
os.
writeByte(
COLOR);
os.
writeLong(
Double.
doubleToLongBits(
c.
getRed()));
os.
writeLong(
Double.
doubleToLongBits(
c.
getGreen()));
os.
writeLong(
Double.
doubleToLongBits(
c.
getBlue()));
os.
writeLong(
Double.
doubleToLongBits(
c.
getOpacity()));
} else if (
value instanceof
Enum) {
final
Enum e = (
Enum)
value;
final int
nameIndex =
stringStore.
addString(
e.
name());
os.
writeByte(
ENUM);
os.
writeShort(
nameIndex);
} else if (
value instanceof
Boolean) {
final
Boolean b = (
Boolean)
value;
os.
writeByte(
BOOLEAN);
os.
writeBoolean(
b);
} else if (
value instanceof
Size) {
final
Size size = (
Size)
value;
os.
writeByte(
SIZE);
final double
sz =
size.
getValue();
final long
val =
Double.
doubleToLongBits(
sz);
os.
writeLong(
val);
final int
index =
stringStore.
addString(
size.
getUnits().
name());
os.
writeShort(
index);
} else if (
value instanceof
String) {
os.
writeByte(
STRING);
final int
index =
stringStore.
addString((
String)
value);
os.
writeShort(
index);
} else if (
value instanceof
URL) {
os.
writeByte(
URL);
final int
index =
stringStore.
addString(
value.
toString());
os.
writeShort(
index);
} else if (
value == null) {
os.
writeByte(
NULL_VALUE);
} else {
throw new
InternalError("cannot writeBinary " + this);
}
}
public static
ParsedValueImpl readBinary(int
bssVersion,
DataInputStream is,
String[]
strings)
throws
IOException {
final boolean
lookup =
is.
readBoolean();
final boolean
hasType =
is.
readBoolean();
final
StyleConverter converter = (
hasType) ?
StyleConverterImpl.
readBinary(
is,
strings) : null;
final int
valType =
is.
readByte();
if (
valType ==
VALUE) {
final
ParsedValueImpl value =
ParsedValueImpl.
readBinary(
bssVersion,
is,
strings);
return new
ParsedValueImpl(
value,
converter,
lookup);
} else if (
valType ==
VALUE_ARRAY) {
if (
bssVersion >= 4) {
// This byte was used to denote whether or not array was all nulls.
// But really, just need to know nVals
is.
readByte();
}
final int
nVals =
is.
readInt();
final
ParsedValueImpl[]
values = (
nVals > 0)
? new
ParsedValueImpl[
nVals]
: null;
for (int
v=0;
v<
nVals;
v++) {
int
vtype =
is.
readByte();
if (
vtype ==
VALUE) {
values[
v] =
ParsedValueImpl.
readBinary(
bssVersion,
is,
strings);
} else {
values[
v] = null;
}
}
return new
ParsedValueImpl(
values,
converter,
lookup);
} else if (
valType ==
ARRAY_OF_VALUE_ARRAY) {
if (
bssVersion >= 4) {
// This byte was used to denote whether or not array was all nulls.
// But really, just need to know nLayers
is.
readByte();
}
final int
nLayers =
is.
readInt();
final
ParsedValueImpl[][]
layers =
nLayers > 0 ? new
ParsedValueImpl[
nLayers][0] : null;
for (int
l=0;
l<
nLayers;
l++) {
if (
bssVersion >= 4) {
// was used to denote whether or not array was all nulls
// but really just need to know nVals
is.
readByte();
}
final int
nVals =
is.
readInt();
layers[
l] =
nVals > 0 ? new
ParsedValueImpl[
nVals] : null;
for (int
v=0;
v<
nVals;
v++) {
int
vtype =
is.
readByte();
if (
vtype ==
VALUE) {
layers[
l][
v] =
ParsedValueImpl.
readBinary(
bssVersion,
is,
strings);
} else {
layers[
l][
v] = null;
}
}
}
return new
ParsedValueImpl(
layers,
converter,
lookup);
} else if (
valType ==
COLOR) {
final double
r =
Double.
longBitsToDouble(
is.
readLong());
final double
g =
Double.
longBitsToDouble(
is.
readLong());
final double
b =
Double.
longBitsToDouble(
is.
readLong());
final double
a =
Double.
longBitsToDouble(
is.
readLong());
return new
ParsedValueImpl<
Color,
Color>(
Color.
color(
r,
g,
b,
a),
converter,
lookup);
} else if (
valType ==
ENUM) {
final int
nameIndex =
is.
readShort();
final
String ename =
strings[
nameIndex];
// Note: this block should be entered _only_ if version 2
if (
bssVersion == 2) {
// RT-31022
// Once upon a time, the enum's class name was added to the
// StringStore and the class name's index was written to the
// stream. Then the writeShort of the class name's index was
// removed but the binary css version wasn't incremented.
// So if we're trying to read a version 2 stream, then we'll
// read this short value. If the stream is actually a the
// version without this short value, then the data will get
// out of sync with the deserialization code and an exception
// will be thrown, at which point we can try a different
// version.
//
int
bad =
is.
readShort();
if (
bad >=
strings.length) throw new
IllegalArgumentException("bad version " +
bssVersion);
}
ParsedValueImpl value = new
ParsedValueImpl(
ename,
converter,
lookup);
return
value;
} else if (
valType ==
BOOLEAN) {
Boolean b =
is.
readBoolean();
return new
ParsedValueImpl<
Boolean,
Boolean>(
b,
converter,
lookup);
} else if (
valType ==
SIZE) {
double
val =
Double.
longBitsToDouble(
is.
readLong());
SizeUnits units =
SizeUnits.
PX;
String unitStr =
strings[
is.
readShort()];
try {
units =
Enum.
valueOf(
SizeUnits.class,
unitStr);
} catch (
IllegalArgumentException iae) {
System.
err.
println(
iae.
toString());
} catch (
NullPointerException npe) {
System.
err.
println(
npe.
toString());
}
return new
ParsedValueImpl<
Size,
Size>(new
Size(
val,
units),
converter,
lookup);
} else if (
valType ==
STRING) {
String str =
strings[
is.
readShort()];
return new
ParsedValueImpl(
str,
converter,
lookup);
} else if (
valType ==
URL) {
String str =
strings[
is.
readShort()];
try {
URL url = new
URL(
str);
return new
ParsedValueImpl(
url,
converter,
lookup);
} catch (
MalformedURLException malf) {
throw new
InternalError("Excpeption in Value.readBinary: " +
malf);
}
} else if (
valType ==
NULL_VALUE) {
return new
ParsedValueImpl(null,
converter,
lookup);
} else {
throw new
InternalError("unknown type: " +
valType);
}
}
}