From 01b2f615acc1089d521313489d731e83cd672858 Mon Sep 17 00:00:00 2001 From: Mario Ivankovits Date: Tue, 26 Sep 2017 15:10:53 +0200 Subject: [PATCH 01/16] Patched TextFlowExt to access Java9 hidden code --- .../demo/hyperlink/HyperlinkDemo.java | 16 ++++- .../demo/hyperlink/TextHyperlinkArea.java | 2 +- .../org/fxmisc/richtext/StyledTextArea.java | 2 +- .../java/org/fxmisc/richtext/TextExt.java | 5 +- .../java/org/fxmisc/richtext/TextFlowExt.java | 46 +++++++------- .../j9adapters/GenericIceBreaker.java | 61 +++++++++++++++++++ .../richtext/j9adapters/RectBounds.java | 10 +++ .../richtext/j9adapters/TextLayout.java | 10 +++ .../fxmisc/richtext/j9adapters/TextLine.java | 10 +++ 9 files changed, 132 insertions(+), 30 deletions(-) create mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java create mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java create mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java create mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java diff --git a/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/HyperlinkDemo.java b/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/HyperlinkDemo.java index 81294bf70..54e1d85dd 100644 --- a/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/HyperlinkDemo.java +++ b/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/HyperlinkDemo.java @@ -1,11 +1,14 @@ package org.fxmisc.richtext.demo.hyperlink; -import com.sun.deploy.uitoolkit.impl.fx.HostServicesFactory; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import org.fxmisc.flowless.VirtualizedScrollPane; +import java.awt.*; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.function.Consumer; /** @@ -21,7 +24,16 @@ public static void main(String[] args) { @Override public void start(Stage primaryStage) { - Consumer showLink = HostServicesFactory.getInstance(this)::showDocument; + Consumer showLink = (string) -> { + try + { + Desktop.getDesktop().browse(new URI(string)); + } + catch (IOException | URISyntaxException e) + { + throw new RuntimeException(e); + } + }; TextHyperlinkArea area = new TextHyperlinkArea(showLink); area.appendText("Some text in the area\n"); diff --git a/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java b/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java index b763ce249..62eb2bfe6 100644 --- a/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java +++ b/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java @@ -65,7 +65,7 @@ public static TextExt createStyledTextNode(Consumer applySegment) { // XXX: binding selectionFill to textFill, // see the note at highlightTextFill - t.impl_selectionFillProperty().bind(t.fillProperty()); + t.selectionFillProperty().bind(t.fillProperty()); return t; } } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java index 015a809cc..51895bcb7 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java @@ -133,7 +133,7 @@ public static Node createStyledTextNode(String text, S style, // XXX: binding selectionFill to textFill, // see the note at highlightTextFill - t.impl_selectionFillProperty().bind(t.fillProperty()); + t.selectionFillProperty().bind(t.fillProperty()); return t; } } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java index 6c9bc3ce6..f8c8721f4 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java @@ -4,14 +4,13 @@ import java.util.Collections; import java.util.List; -import com.sun.javafx.css.converters.EnumConverter; -import com.sun.javafx.css.converters.SizeConverter; - import javafx.beans.property.ObjectProperty; import javafx.css.CssMetaData; import javafx.css.StyleConverter; import javafx.css.Styleable; import javafx.css.StyleableObjectProperty; +import javafx.css.converter.EnumConverter; +import javafx.css.converter.SizeConverter; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.StrokeLineCap; diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java index f481026b5..c25e92666 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java @@ -7,7 +7,13 @@ import java.util.ArrayList; import java.util.List; +import javafx.geometry.Point2D; import javafx.scene.control.IndexRange; +import javafx.scene.text.HitInfo; +import org.fxmisc.richtext.j9adapters.GenericIceBreaker; +import org.fxmisc.richtext.j9adapters.RectBounds; +import org.fxmisc.richtext.j9adapters.TextLayout; +import org.fxmisc.richtext.j9adapters.TextLine; import org.fxmisc.richtext.model.TwoLevelNavigator; import javafx.scene.shape.PathElement; @@ -15,34 +21,22 @@ import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; -import com.sun.javafx.geom.RectBounds; -import com.sun.javafx.scene.text.HitInfo; -import com.sun.javafx.scene.text.TextLayout; -import com.sun.javafx.text.PrismTextLayout; -import com.sun.javafx.text.TextLine; - /** * Adds additional API to {@link TextFlow}. */ class TextFlowExt extends TextFlow { private static Method mGetTextLayout; - private static Method mGetLines; - private static Method mGetLineIndex; - private static Method mGetCharCount; + private static Method mGetRange; static { try { mGetTextLayout = TextFlow.class.getDeclaredMethod("getTextLayout"); - mGetLines = PrismTextLayout.class.getDeclaredMethod("getLines"); - mGetLineIndex = PrismTextLayout.class.getDeclaredMethod("getLineIndex", float.class); - mGetCharCount = PrismTextLayout.class.getDeclaredMethod("getCharCount"); + mGetRange = TextFlow.class.getDeclaredMethod("getRange", int.class, int.class, int.class); } catch (NoSuchMethodException | SecurityException e) { throw new RuntimeException(e); } + mGetRange.setAccessible(true); mGetTextLayout.setAccessible(true); - mGetLines.setAccessible(true); - mGetLineIndex.setAccessible(true); - mGetCharCount.setAccessible(true); } private static Object invoke(Method m, Object obj, Object... args) { @@ -87,7 +81,7 @@ int getLineOfCharacter(int charIdx) { } PathElement[] getCaretShape(int charIdx, boolean isLeading) { - return textLayout().getCaretShape(charIdx, isLeading, 0.0f, 0.0f); + return caretShape(charIdx, isLeading); } PathElement[] getRangeShape(IndexRange range) { @@ -95,7 +89,7 @@ PathElement[] getRangeShape(IndexRange range) { } PathElement[] getRangeShape(int from, int to) { - return textLayout().getRange(from, to, TextLayout.TYPE_TEXT, 0, 0); + return rangeShape(from, to); } PathElement[] getUnderlineShape(IndexRange range) { @@ -110,7 +104,13 @@ PathElement[] getUnderlineShape(IndexRange range) { */ PathElement[] getUnderlineShape(int from, int to) { // get a Path for the text underline - PathElement[] shape = textLayout().getRange(from, to, TextLayout.TYPE_UNDERLINE, 0, 0); + PathElement[] shape; + try { + shape = (PathElement[]) mGetRange.invoke(this, from, to, 1 << 1); + } + catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(); + } // The shape is returned as a closed Path (a thin rectangle). // If we use the Path as it is, this causes rendering issues. @@ -139,7 +139,7 @@ CharacterHit hitLine(double x, int lineIndex) { } CharacterHit hit(double x, double y) { - HitInfo hit = textLayout().getHitInfo((float) x, (float) y); + HitInfo hit = hitTest(new Point2D(x, y)); int charIdx = hit.getCharIndex(); boolean leading = hit.isLeading(); @@ -193,18 +193,18 @@ private float getLineCenter(int index) { } private TextLine[] getLines() { - return (TextLine[]) invoke(mGetLines, textLayout()); + return textLayout().getLines(); } private int getLineIndex(float y) { - return (int) invoke(mGetLineIndex, textLayout(), y); + return textLayout().getLineIndex(y); } private int getCharCount() { - return (int) invoke(mGetCharCount, textLayout()); + return textLayout().getCharCount(); } private TextLayout textLayout() { - return (TextLayout) invoke(mGetTextLayout, this); + return GenericIceBreaker.proxy(TextLayout.class, invoke(mGetTextLayout, this)); } } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java new file mode 100644 index 000000000..ff17f395a --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java @@ -0,0 +1,61 @@ +package org.fxmisc.richtext.j9adapters; + +import java.lang.reflect.*; + +public class GenericIceBreaker implements InvocationHandler { + private final Object delegate; + + public GenericIceBreaker(Object delegate) { + this.delegate = delegate; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Method delegateMethod = delegate.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); + if (!delegateMethod.canAccess(delegate)) { + delegateMethod.setAccessible(true); + } + + Object delegateMethodReturn = null; + try { + delegateMethodReturn = delegateMethod.invoke(delegate, args); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException("problems invoking " + method.getName()); + } + if (delegateMethodReturn == null) { + return null; + } + + if (method.getReturnType().isArray()) { + if (method.getReturnType().getComponentType().isInterface() + && !method.getReturnType().getComponentType().equals(delegateMethod.getReturnType().getComponentType())) { + + int arrayLength = Array.getLength(delegateMethodReturn); + Object retArray = Array.newInstance(method.getReturnType().getComponentType(), arrayLength); + for (int i = 0; i < arrayLength; i++) { + Array.set(retArray, + i, + proxy( + method.getReturnType().getComponentType(), + Array.get(delegateMethodReturn, i))); + } + + return retArray; + } + } + + if (method.getReturnType().isInterface() + && !method.getReturnType().equals(delegateMethod.getReturnType())) { + return proxy(method.getReturnType(), delegateMethodReturn); + } + + return delegateMethodReturn; + } + + public static T proxy(Class iface, Object delegate) { + return (T) Proxy.newProxyInstance( + iface.getClassLoader(), + new Class[]{iface}, + new GenericIceBreaker(delegate)); + } +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java new file mode 100644 index 000000000..134b40b12 --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java @@ -0,0 +1,10 @@ +package org.fxmisc.richtext.j9adapters; + +public interface RectBounds +{ + float getMinX(); + + float getMaxX(); + + float getHeight(); +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java new file mode 100644 index 000000000..156d1e5ec --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java @@ -0,0 +1,10 @@ +package org.fxmisc.richtext.j9adapters; + +public interface TextLayout +{ + TextLine[] getLines(); + + int getLineIndex(float y); + + int getCharCount(); +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java new file mode 100644 index 000000000..372c84cab --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java @@ -0,0 +1,10 @@ +package org.fxmisc.richtext.j9adapters; + +public interface TextLine +{ + int getLength(); + + RectBounds getBounds(); + + int getStart(); +} From 749db5ecd40a488904297561b96abeb737bcfb19 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 14 Oct 2017 16:22:30 +0200 Subject: [PATCH 02/16] use reflection for JavaFX 8 and 9 compatibility of StyledTextArea and TextExt --- .../fxmisc/richtext/JavaFXCompatibility.java | 93 +++++++++++++++++++ .../org/fxmisc/richtext/StyledTextArea.java | 2 +- .../java/org/fxmisc/richtext/TextExt.java | 12 +-- 3 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java new file mode 100644 index 000000000..982902189 --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java @@ -0,0 +1,93 @@ +package org.fxmisc.richtext; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.StringTokenizer; +import javafx.beans.property.ObjectProperty; +import javafx.css.StyleConverter; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; + +class JavaFXCompatibility { + + private static boolean isJava9orLater; + + static { + try { + // Java 9 version-String Scheme: http://openjdk.java.net/jeps/223 + StringTokenizer st = new StringTokenizer(System.getProperty("java.version"), "._-+"); + int majorVersion = Integer.parseInt(st.nextToken()); + isJava9orLater = majorVersion >= 9; + } catch (Exception e) { + // Java 8 or older + } + } + + /** + * Java 8: ObjectProperty javafx.scene.text.Text.impl_selectionFillProperty() + * Java 9+: ObjectProperty javafx.scene.text.Text.selectionFillProperty() + */ + @SuppressWarnings("unchecked") + static ObjectProperty Text_selectionFillProperty(Text text) { + try { + if (mText_selectionFillProperty == null) { + mText_selectionFillProperty = Text.class.getMethod( + isJava9orLater ? "selectionFillProperty" : "impl_selectionFillProperty"); + } + return (ObjectProperty) mText_selectionFillProperty.invoke(text); + } catch(NoSuchMethodException | SecurityException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + throw new Error(e); + } + } + + private static Method mText_selectionFillProperty; + + /** + * Java 8: com.sun.javafx.css.converters.SizeConverter.SequenceConverter.getInstance() + * Java 9+: javafx.css.converter.SizeConverter.SequenceConverter.getInstance() + */ + @SuppressWarnings("unchecked") + static StyleConverter SizeConverter_SequenceConverter_getInstance() { + try { + if (mSizeConverter_SequenceConverter_getInstance == null) { + Class c = Class.forName(isJava9orLater + ? "javafx.css.converter.SizeConverter$SequenceConverter" + : "com.sun.javafx.css.converters.SizeConverter$SequenceConverter"); + mSizeConverter_SequenceConverter_getInstance = c.getMethod("getInstance"); + } + return (StyleConverter) mSizeConverter_SequenceConverter_getInstance.invoke(null); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | + IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + throw new Error(e); + } + } + + private static Method mSizeConverter_SequenceConverter_getInstance; + + /** + * Java 8: new com.sun.javafx.css.converters.EnumConverter(Class enumClass) + * Java 9+: new javafx.css.converter.EnumConverter(Class enumClass) + */ + @SuppressWarnings("unchecked") + static StyleConverter new_EnumConverter(Class enumClass) { + try { + if (mEnumConverter_newInstance == null) { + Class c = Class.forName(isJava9orLater + ? "javafx.css.converter.EnumConverter" + : "com.sun.javafx.css.converters.EnumConverter"); + mEnumConverter_newInstance = c.getConstructor(Class.class); + } + return (StyleConverter) mEnumConverter_newInstance.newInstance(enumClass); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | + IllegalArgumentException | InvocationTargetException | InstantiationException e) { + e.printStackTrace(); + throw new Error(e); + } + } + + private static Constructor mEnumConverter_newInstance; +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java index 51895bcb7..188d62f8e 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextArea.java @@ -133,7 +133,7 @@ public static Node createStyledTextNode(String text, S style, // XXX: binding selectionFill to textFill, // see the note at highlightTextFill - t.selectionFillProperty().bind(t.fillProperty()); + JavaFXCompatibility.Text_selectionFillProperty(t).bind(t.fillProperty()); return t; } } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java index f8c8721f4..2f7c60206 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java @@ -9,8 +9,6 @@ import javafx.css.StyleConverter; import javafx.css.Styleable; import javafx.css.StyleableObjectProperty; -import javafx.css.converter.EnumConverter; -import javafx.css.converter.SizeConverter; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.StrokeLineCap; @@ -225,7 +223,7 @@ public ObjectProperty borderStrokeColorProperty() { */ public ObjectProperty underlineWidthProperty() { return underlineWidth; } - // Dash array for the text underline + // Dash array for the text underline public Number[] getUnderlineDashArray() { return underlineDashArray.get(); } public void setUnderlineDashArray(Number[] dashArray) { underlineDashArray.set(dashArray); } @@ -274,12 +272,12 @@ private static class StyleableProperties { ); private static final CssMetaData BORDER_TYPE = new CustomCssMetaData<>( - "-rtfx-border-stroke-type", new EnumConverter<>(StrokeType.class), + "-rtfx-border-stroke-type", JavaFXCompatibility.new_EnumConverter(StrokeType.class), StrokeType.INSIDE, n -> n.borderStrokeType ); private static final CssMetaData BORDER_DASH_ARRAY = new CustomCssMetaData<>( - "-rtfx-border-stroke-dash-array", SizeConverter.SequenceConverter.getInstance(), + "-rtfx-border-stroke-dash-array", JavaFXCompatibility.SizeConverter_SequenceConverter_getInstance(), new Double[0], n -> n.borderStrokeDashArray ); @@ -294,12 +292,12 @@ private static class StyleableProperties { ); private static final CssMetaData UNDERLINE_DASH_ARRAY = new CustomCssMetaData<>( - "-rtfx-underline-dash-array", SizeConverter.SequenceConverter.getInstance(), + "-rtfx-underline-dash-array", JavaFXCompatibility.SizeConverter_SequenceConverter_getInstance(), new Double[0], n -> n.underlineDashArray ); private static final CssMetaData UNDERLINE_CAP = new CustomCssMetaData<>( - "-rtfx-underline-cap", new EnumConverter(StrokeLineCap.class), + "-rtfx-underline-cap", JavaFXCompatibility.new_EnumConverter(StrokeLineCap.class), StrokeLineCap.SQUARE, n -> n.underlineCap ); } From 9f3c604f62218cffa35962da4bf538f501693e92 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 14 Oct 2017 18:39:57 +0200 Subject: [PATCH 03/16] made TextFlowExt compatible to JavaFX 8 --- .../java/org/fxmisc/richtext/TextFlowExt.java | 26 ++++++++----------- .../j9adapters/GenericIceBreaker.java | 4 +-- .../fxmisc/richtext/j9adapters/HitInfo.java | 7 +++++ .../richtext/j9adapters/TextLayout.java | 9 +++++++ 4 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java index c25e92666..a35dc3ef7 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java @@ -7,10 +7,9 @@ import java.util.ArrayList; import java.util.List; -import javafx.geometry.Point2D; import javafx.scene.control.IndexRange; -import javafx.scene.text.HitInfo; import org.fxmisc.richtext.j9adapters.GenericIceBreaker; +import org.fxmisc.richtext.j9adapters.HitInfo; import org.fxmisc.richtext.j9adapters.RectBounds; import org.fxmisc.richtext.j9adapters.TextLayout; import org.fxmisc.richtext.j9adapters.TextLine; @@ -27,15 +26,12 @@ class TextFlowExt extends TextFlow { private static Method mGetTextLayout; - private static Method mGetRange; static { try { mGetTextLayout = TextFlow.class.getDeclaredMethod("getTextLayout"); - mGetRange = TextFlow.class.getDeclaredMethod("getRange", int.class, int.class, int.class); } catch (NoSuchMethodException | SecurityException e) { throw new RuntimeException(e); } - mGetRange.setAccessible(true); mGetTextLayout.setAccessible(true); } @@ -81,7 +77,9 @@ int getLineOfCharacter(int charIdx) { } PathElement[] getCaretShape(int charIdx, boolean isLeading) { - return caretShape(charIdx, isLeading); +// use if Java 9 becomes minimum requirement +// return caretShape(charIdx, isLeading); + return textLayout().getCaretShape(charIdx, isLeading, 0, 0); } PathElement[] getRangeShape(IndexRange range) { @@ -89,7 +87,9 @@ PathElement[] getRangeShape(IndexRange range) { } PathElement[] getRangeShape(int from, int to) { - return rangeShape(from, to); +// use if Java 9 becomes minimum requirement +// return rangeShape(from, to); + return textLayout().getRange(from, to, TextLayout.TYPE_TEXT, 0, 0); } PathElement[] getUnderlineShape(IndexRange range) { @@ -104,13 +104,7 @@ PathElement[] getUnderlineShape(IndexRange range) { */ PathElement[] getUnderlineShape(int from, int to) { // get a Path for the text underline - PathElement[] shape; - try { - shape = (PathElement[]) mGetRange.invoke(this, from, to, 1 << 1); - } - catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(); - } + PathElement[] shape = textLayout().getRange(from, to, TextLayout.TYPE_UNDERLINE, 0, 0); // The shape is returned as a closed Path (a thin rectangle). // If we use the Path as it is, this causes rendering issues. @@ -139,7 +133,9 @@ CharacterHit hitLine(double x, int lineIndex) { } CharacterHit hit(double x, double y) { - HitInfo hit = hitTest(new Point2D(x, y)); +// use if Java 9 becomes minimum requirement +// HitInfo hit = hitTest(new Point2D(x, y)); + HitInfo hit = textLayout().getHitInfo((float) x, (float) y); int charIdx = hit.getCharIndex(); boolean leading = hit.isLeading(); diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java index ff17f395a..a5afa4d4a 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java @@ -12,9 +12,9 @@ public GenericIceBreaker(Object delegate) { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Method delegateMethod = delegate.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); - if (!delegateMethod.canAccess(delegate)) { +//TODO if (!delegateMethod.canAccess(delegate)) { delegateMethod.setAccessible(true); - } +// } Object delegateMethodReturn = null; try { diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java new file mode 100644 index 000000000..c83e817cb --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java @@ -0,0 +1,7 @@ +package org.fxmisc.richtext.j9adapters; + +public interface HitInfo +{ + int getCharIndex(); + boolean isLeading(); +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java index 156d1e5ec..100f9ae5e 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java @@ -1,10 +1,19 @@ package org.fxmisc.richtext.j9adapters; +import javafx.scene.shape.PathElement; + public interface TextLayout { + public static final int TYPE_TEXT = 1 << 0; + public static final int TYPE_UNDERLINE = 1 << 1; + TextLine[] getLines(); int getLineIndex(float y); int getCharCount(); + + HitInfo getHitInfo(float x, float y); + PathElement[] getCaretShape(int offset, boolean isLeading, float x, float y); + PathElement[] getRange(int start, int end, int type, float x, float y); } From 93be11a9629ad77d7b6a4f981c3a00746f2dc62c Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 14 Oct 2017 22:09:30 +0200 Subject: [PATCH 04/16] use StyleConverter.getEnumConverter() instead of reflection --- .../fxmisc/richtext/JavaFXCompatibility.java | 24 ------------------- .../java/org/fxmisc/richtext/TextExt.java | 4 ++-- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java index 982902189..dae65e3c8 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java @@ -1,6 +1,5 @@ package org.fxmisc.richtext; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.StringTokenizer; @@ -67,27 +66,4 @@ static StyleConverter SizeConverter_SequenceConverter_getInstance() } private static Method mSizeConverter_SequenceConverter_getInstance; - - /** - * Java 8: new com.sun.javafx.css.converters.EnumConverter(Class enumClass) - * Java 9+: new javafx.css.converter.EnumConverter(Class enumClass) - */ - @SuppressWarnings("unchecked") - static StyleConverter new_EnumConverter(Class enumClass) { - try { - if (mEnumConverter_newInstance == null) { - Class c = Class.forName(isJava9orLater - ? "javafx.css.converter.EnumConverter" - : "com.sun.javafx.css.converters.EnumConverter"); - mEnumConverter_newInstance = c.getConstructor(Class.class); - } - return (StyleConverter) mEnumConverter_newInstance.newInstance(enumClass); - } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | - IllegalArgumentException | InvocationTargetException | InstantiationException e) { - e.printStackTrace(); - throw new Error(e); - } - } - - private static Constructor mEnumConverter_newInstance; } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java index 2f7c60206..702553067 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java @@ -272,7 +272,7 @@ private static class StyleableProperties { ); private static final CssMetaData BORDER_TYPE = new CustomCssMetaData<>( - "-rtfx-border-stroke-type", JavaFXCompatibility.new_EnumConverter(StrokeType.class), + "-rtfx-border-stroke-type", (StyleConverter) StyleConverter.getEnumConverter(StrokeType.class), StrokeType.INSIDE, n -> n.borderStrokeType ); @@ -297,7 +297,7 @@ private static class StyleableProperties { ); private static final CssMetaData UNDERLINE_CAP = new CustomCssMetaData<>( - "-rtfx-underline-cap", JavaFXCompatibility.new_EnumConverter(StrokeLineCap.class), + "-rtfx-underline-cap", (StyleConverter) StyleConverter.getEnumConverter(StrokeLineCap.class), StrokeLineCap.SQUARE, n -> n.underlineCap ); } From 69b2b1bd93baf6f51ee1b88e0485709421f9ebf6 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 14 Oct 2017 22:24:55 +0200 Subject: [PATCH 05/16] made TextHyperlinkArea compatible to JavaFX 8 --- .../org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java | 3 ++- .../main/java/org/fxmisc/richtext/JavaFXCompatibility.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java b/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java index 62eb2bfe6..f7fcddaf3 100644 --- a/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java +++ b/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/hyperlink/TextHyperlinkArea.java @@ -2,6 +2,7 @@ import javafx.geometry.VPos; import org.fxmisc.richtext.GenericStyledArea; +import org.fxmisc.richtext.JavaFXCompatibility; import org.fxmisc.richtext.TextExt; import org.fxmisc.richtext.model.ReadOnlyStyledDocument; import org.fxmisc.richtext.model.SegmentOps; @@ -65,7 +66,7 @@ public static TextExt createStyledTextNode(Consumer applySegment) { // XXX: binding selectionFill to textFill, // see the note at highlightTextFill - t.selectionFillProperty().bind(t.fillProperty()); + JavaFXCompatibility.Text_selectionFillProperty(t).bind(t.fillProperty()); return t; } } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java index dae65e3c8..ccf9e83c1 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java @@ -8,7 +8,7 @@ import javafx.scene.paint.Paint; import javafx.scene.text.Text; -class JavaFXCompatibility { +public class JavaFXCompatibility { private static boolean isJava9orLater; @@ -28,7 +28,7 @@ class JavaFXCompatibility { * Java 9+: ObjectProperty javafx.scene.text.Text.selectionFillProperty() */ @SuppressWarnings("unchecked") - static ObjectProperty Text_selectionFillProperty(Text text) { + public static ObjectProperty Text_selectionFillProperty(Text text) { try { if (mText_selectionFillProperty == null) { mText_selectionFillProperty = Text.class.getMethod( From ec81245c71f5001421fef8f308936070f892d117 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 14 Oct 2017 23:03:23 +0200 Subject: [PATCH 06/16] fixed javadoc error in class JavaFXCompatibility --- .../main/java/org/fxmisc/richtext/JavaFXCompatibility.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java index ccf9e83c1..cf097ab45 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java @@ -24,8 +24,8 @@ public class JavaFXCompatibility { } /** - * Java 8: ObjectProperty javafx.scene.text.Text.impl_selectionFillProperty() - * Java 9+: ObjectProperty javafx.scene.text.Text.selectionFillProperty() + * Java 8: javafx.scene.text.Text.impl_selectionFillProperty() + * Java 9+: javafx.scene.text.Text.selectionFillProperty() */ @SuppressWarnings("unchecked") public static ObjectProperty Text_selectionFillProperty(Text text) { From 62335ea62c58179b32983646029dcdb626b97097 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sat, 14 Oct 2017 23:58:20 +0200 Subject: [PATCH 07/16] Travis: also build on Java 9 to detect compatibility issues --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4760cc98d..1837a1965 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,9 @@ matrix: - os: linux dist: trusty jdk: oraclejdk8 + - os: linux + dist: trusty + jdk: oraclejdk9 # Headless Build: xvfb + TestFX does not work on Travis OSX environments (as of 2017-05-24) - os: osx From 7fde186264548b46439e12b82047c62694d8d202 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 15 Oct 2017 00:32:08 +0200 Subject: [PATCH 08/16] use TestFX 4.0.8-alpha for Java 9 compatibility --- richtextfx/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/richtextfx/build.gradle b/richtextfx/build.gradle index 6ffaf80d5..80e1a606a 100644 --- a/richtextfx/build.gradle +++ b/richtextfx/build.gradle @@ -34,8 +34,8 @@ dependencies { integrationTestCompile group: 'junit', name: 'junit', version: '4.12' integrationTestCompile group: 'com.nitorcreations', name: 'junit-runners', version: '1.2' - integrationTestCompile "org.testfx:testfx-core:4.0.6-alpha" - integrationTestCompile ("org.testfx:testfx-junit:4.0.6-alpha") { + integrationTestCompile "org.testfx:testfx-core:4.0.8-alpha" + integrationTestCompile ("org.testfx:testfx-junit:4.0.8-alpha") { exclude(group: "junit", module: "junit") } integrationTestCompile "org.testfx:openjfx-monocle:8u76-b04" From a2d7da3edc4b201e055a49ca87e72abe106fc6e5 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 15 Oct 2017 11:39:19 +0200 Subject: [PATCH 09/16] cache methods in GenericIceBreaker for performance --- .../j9adapters/GenericIceBreaker.java | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java index a5afa4d4a..03801bc4d 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java @@ -1,6 +1,8 @@ package org.fxmisc.richtext.j9adapters; import java.lang.reflect.*; +import java.util.Arrays; +import java.util.HashMap; public class GenericIceBreaker implements InvocationHandler { private final Object delegate; @@ -11,7 +13,7 @@ public GenericIceBreaker(Object delegate) { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Method delegateMethod = delegate.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); + Method delegateMethod = getDeclaredMethod(delegate.getClass(), method.getName(), method.getParameterTypes()); //TODO if (!delegateMethod.canAccess(delegate)) { delegateMethod.setAccessible(true); // } @@ -58,4 +60,51 @@ public static T proxy(Class iface, Object delegate) { new Class[]{iface}, new GenericIceBreaker(delegate)); } + + /* ********************************************************************** * + * * + * Method cache * + * * + * ********************************************************************** */ + + private static final HashMap declaredMethodCache = new HashMap<>(); + + private static synchronized Method getDeclaredMethod(Class cls, String name, Class... paramTypes) + throws NoSuchMethodException, SecurityException + { + MethodCacheKey methodCacheKey = new MethodCacheKey(cls, name, paramTypes); + + Method m = declaredMethodCache.get(methodCacheKey); + if (m == null) { + m = cls.getDeclaredMethod(name, paramTypes); + declaredMethodCache.put(methodCacheKey, m); + } + return m; + } + + private static class MethodCacheKey { + final Class cls; + final String name; + final Class[] paramTypes; + + public MethodCacheKey(Class cls, String name, Class... paramTypes) { + this.cls = cls; + this.name = name; + this.paramTypes = paramTypes; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MethodCacheKey)) + return false; + + MethodCacheKey key2 = (MethodCacheKey) obj; + return cls == key2.cls && name.equals(key2.name) && Arrays.equals(paramTypes, key2.paramTypes); + } + + @Override + public int hashCode() { + return cls.hashCode() + name.hashCode() + Arrays.hashCode(paramTypes); + } + } } From a590bb396ea25bb776add74278fea393e603023e Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 16 Oct 2017 14:04:22 +0200 Subject: [PATCH 10/16] invoke m.setAccessible(true) only once for cached method --- .../org/fxmisc/richtext/j9adapters/GenericIceBreaker.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java index 03801bc4d..a87e67b5b 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java @@ -14,9 +14,6 @@ public GenericIceBreaker(Object delegate) { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Method delegateMethod = getDeclaredMethod(delegate.getClass(), method.getName(), method.getParameterTypes()); -//TODO if (!delegateMethod.canAccess(delegate)) { - delegateMethod.setAccessible(true); -// } Object delegateMethodReturn = null; try { @@ -77,6 +74,7 @@ private static synchronized Method getDeclaredMethod(Class cls, String name, Method m = declaredMethodCache.get(methodCacheKey); if (m == null) { m = cls.getDeclaredMethod(name, paramTypes); + m.setAccessible(true); declaredMethodCache.put(methodCacheKey, m); } return m; From 631ea2c3b9e22dfdc1ee95ed0113a115e817927f Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 17 Oct 2017 09:31:36 +0200 Subject: [PATCH 11/16] made class JavaFXCompatibility package private again (no longer needed for demos since merging PR #617) --- .../main/java/org/fxmisc/richtext/JavaFXCompatibility.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java index cf097ab45..4dd14ab75 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java @@ -8,7 +8,7 @@ import javafx.scene.paint.Paint; import javafx.scene.text.Text; -public class JavaFXCompatibility { +class JavaFXCompatibility { private static boolean isJava9orLater; @@ -28,7 +28,7 @@ public class JavaFXCompatibility { * Java 9+: javafx.scene.text.Text.selectionFillProperty() */ @SuppressWarnings("unchecked") - public static ObjectProperty Text_selectionFillProperty(Text text) { + static ObjectProperty Text_selectionFillProperty(Text text) { try { if (mText_selectionFillProperty == null) { mText_selectionFillProperty = Text.class.getMethod( From 06a7bdf7fd80edca9b56819fc147b97edfb2e6a4 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 17 Oct 2017 10:16:51 +0200 Subject: [PATCH 12/16] Travis: set gradle source/targetCompatibility to 9 when building on oraclejdk9 to detect Java 9 compatibility issues in source code --- .travis.yml | 1 + build.gradle | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1837a1965..74cbeac70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ matrix: - os: linux dist: trusty jdk: oraclejdk9 + env: _JAVA_OPTIONS="-DcheckSourceCompatibility=9" # Headless Build: xvfb + TestFX does not work on Travis OSX environments (as of 2017-05-24) - os: osx diff --git a/build.gradle b/build.gradle index bdf2b82ee..7dd36b5c7 100644 --- a/build.gradle +++ b/build.gradle @@ -15,5 +15,13 @@ subprojects { sourceCompatibility = '1.8' targetCompatibility = '1.8' + // allow setting source/target compatibility from command line + // for checking Java 9+ compatibility in Travis CI + def checkSourceCompatibility = System.properties["checkSourceCompatibility"] + if (checkSourceCompatibility != null) { + sourceCompatibility = checkSourceCompatibility + targetCompatibility = checkSourceCompatibility + } + compileJava.options.deprecation = true } From 80ad8b6050e159ac9f70d2ab48e76c4d82904db0 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 17 Oct 2017 10:24:20 +0200 Subject: [PATCH 13/16] include testfx-internal-java9 when Gradle is running in Java 9 VM --- richtextfx/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/richtextfx/build.gradle b/richtextfx/build.gradle index 80e1a606a..3c5628695 100644 --- a/richtextfx/build.gradle +++ b/richtextfx/build.gradle @@ -35,6 +35,9 @@ dependencies { integrationTestCompile group: 'junit', name: 'junit', version: '4.12' integrationTestCompile group: 'com.nitorcreations', name: 'junit-runners', version: '1.2' integrationTestCompile "org.testfx:testfx-core:4.0.8-alpha" + if (org.gradle.api.JavaVersion.current().isJava9()) { + integrationTestCompile "org.testfx:testfx-internal-java9:4.0.8-alpha" + } integrationTestCompile ("org.testfx:testfx-junit:4.0.8-alpha") { exclude(group: "junit", module: "junit") } From b083c723bbf8ab791aae4aa1dcf1d4d385319070 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 17 Oct 2017 10:35:51 +0200 Subject: [PATCH 14/16] exclude package org.fxmisc.richtext.j9adapters from javadoc --- richtextfx/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/richtextfx/build.gradle b/richtextfx/build.gradle index 3c5628695..60d275399 100644 --- a/richtextfx/build.gradle +++ b/richtextfx/build.gradle @@ -73,6 +73,8 @@ javadoc { // resolve links to Flowless 'https://fxmisc.github.io/flowless/javadoc/0.5/' ] + + exclude('**/j9adapters/**') } // use test logging. From df0eca764b05e84dc4f66e2d20df578b18f1db99 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 17 Oct 2017 11:48:19 +0200 Subject: [PATCH 15/16] changed tabs to spaces; changed line endings of HitInfo.java from CRLF to LF --- .../org/fxmisc/richtext/j9adapters/HitInfo.java | 14 +++++++------- .../org/fxmisc/richtext/j9adapters/TextLayout.java | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java index c83e817cb..20969d8b0 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java @@ -1,7 +1,7 @@ -package org.fxmisc.richtext.j9adapters; - -public interface HitInfo -{ - int getCharIndex(); - boolean isLeading(); -} +package org.fxmisc.richtext.j9adapters; + +public interface HitInfo +{ + int getCharIndex(); + boolean isLeading(); +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java index 100f9ae5e..1d9e7287f 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java @@ -4,8 +4,8 @@ public interface TextLayout { - public static final int TYPE_TEXT = 1 << 0; - public static final int TYPE_UNDERLINE = 1 << 1; + public static final int TYPE_TEXT = 1 << 0; + public static final int TYPE_UNDERLINE = 1 << 1; TextLine[] getLines(); From aaee8333c636c87b91ec2c0b0567a53df0c1d48b Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 18 Oct 2017 18:34:31 +0200 Subject: [PATCH 16/16] moved classes/interfaces from package j9adapters into class TextFlowExt as private nested classes/interfaces --- richtextfx/build.gradle | 2 - .../java/org/fxmisc/richtext/TextFlowExt.java | 157 +++++++++++++++++- .../j9adapters/GenericIceBreaker.java | 108 ------------ .../fxmisc/richtext/j9adapters/HitInfo.java | 7 - .../richtext/j9adapters/RectBounds.java | 10 -- .../richtext/j9adapters/TextLayout.java | 19 --- .../fxmisc/richtext/j9adapters/TextLine.java | 10 -- 7 files changed, 150 insertions(+), 163 deletions(-) delete mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java delete mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java delete mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java delete mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java delete mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java diff --git a/richtextfx/build.gradle b/richtextfx/build.gradle index 60d275399..3c5628695 100644 --- a/richtextfx/build.gradle +++ b/richtextfx/build.gradle @@ -73,8 +73,6 @@ javadoc { // resolve links to Flowless 'https://fxmisc.github.io/flowless/javadoc/0.5/' ] - - exclude('**/j9adapters/**') } // use test logging. diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java index a35dc3ef7..7d0f1b81a 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java @@ -1,20 +1,19 @@ package org.fxmisc.richtext; import static org.fxmisc.richtext.model.TwoDimensional.Bias.*; - +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; -import javafx.scene.control.IndexRange; -import org.fxmisc.richtext.j9adapters.GenericIceBreaker; -import org.fxmisc.richtext.j9adapters.HitInfo; -import org.fxmisc.richtext.j9adapters.RectBounds; -import org.fxmisc.richtext.j9adapters.TextLayout; -import org.fxmisc.richtext.j9adapters.TextLine; import org.fxmisc.richtext.model.TwoLevelNavigator; +import javafx.scene.control.IndexRange; import javafx.scene.shape.PathElement; import javafx.scene.text.TextFlow; import javafx.scene.shape.LineTo; @@ -203,4 +202,148 @@ private int getCharCount() { private TextLayout textLayout() { return GenericIceBreaker.proxy(TextLayout.class, invoke(mGetTextLayout, this)); } + + /* ********************************************************************** * + * * + * GenericIceBreaker * + * * + * ********************************************************************** */ + + private static class GenericIceBreaker implements InvocationHandler { + private final Object delegate; + + private GenericIceBreaker(Object delegate) { + this.delegate = delegate; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Method delegateMethod = getDeclaredMethod(delegate.getClass(), method.getName(), method.getParameterTypes()); + + Object delegateMethodReturn = null; + try { + delegateMethodReturn = delegateMethod.invoke(delegate, args); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException("problems invoking " + method.getName()); + } + if (delegateMethodReturn == null) { + return null; + } + + if (method.getReturnType().isArray()) { + if (method.getReturnType().getComponentType().isInterface() + && !method.getReturnType().getComponentType().equals(delegateMethod.getReturnType().getComponentType())) { + + int arrayLength = Array.getLength(delegateMethodReturn); + Object retArray = Array.newInstance(method.getReturnType().getComponentType(), arrayLength); + for (int i = 0; i < arrayLength; i++) { + Array.set(retArray, + i, + proxy( + method.getReturnType().getComponentType(), + Array.get(delegateMethodReturn, i))); + } + + return retArray; + } + } + + if (method.getReturnType().isInterface() + && !method.getReturnType().equals(delegateMethod.getReturnType())) { + return proxy(method.getReturnType(), delegateMethodReturn); + } + + return delegateMethodReturn; + } + + @SuppressWarnings("unchecked") + static T proxy(Class iface, Object delegate) { + return (T) Proxy.newProxyInstance( + iface.getClassLoader(), + new Class[]{iface}, + new GenericIceBreaker(delegate)); + } + + private static final HashMap declaredMethodCache = new HashMap<>(); + + private static synchronized Method getDeclaredMethod(Class cls, String name, Class... paramTypes) + throws NoSuchMethodException, SecurityException + { + MethodCacheKey methodCacheKey = new MethodCacheKey(cls, name, paramTypes); + + Method m = declaredMethodCache.get(methodCacheKey); + if (m == null) { + m = cls.getDeclaredMethod(name, paramTypes); + m.setAccessible(true); + declaredMethodCache.put(methodCacheKey, m); + } + return m; + } + } + + private static class MethodCacheKey { + final Class cls; + final String name; + final Class[] paramTypes; + + MethodCacheKey(Class cls, String name, Class... paramTypes) { + this.cls = cls; + this.name = name; + this.paramTypes = paramTypes; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MethodCacheKey)) + return false; + + MethodCacheKey key2 = (MethodCacheKey) obj; + return cls == key2.cls && name.equals(key2.name) && Arrays.equals(paramTypes, key2.paramTypes); + } + + @Override + public int hashCode() { + return cls.hashCode() + name.hashCode() + Arrays.hashCode(paramTypes); + } + } + + /* ********************************************************************** * + * * + * Proxy interfaces * + * * + * ********************************************************************** */ + + private interface TextLayout + { + static final int TYPE_TEXT = 1 << 0; + static final int TYPE_UNDERLINE = 1 << 1; + + TextLine[] getLines(); + int getLineIndex(float y); + int getCharCount(); + + HitInfo getHitInfo(float x, float y); + PathElement[] getCaretShape(int offset, boolean isLeading, float x, float y); + PathElement[] getRange(int start, int end, int type, float x, float y); + } + + private interface TextLine + { + int getLength(); + RectBounds getBounds(); + int getStart(); + } + + private interface RectBounds + { + float getMinX(); + float getMaxX(); + float getHeight(); + } + + private interface HitInfo + { + int getCharIndex(); + boolean isLeading(); + } } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java deleted file mode 100644 index a87e67b5b..000000000 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/GenericIceBreaker.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.fxmisc.richtext.j9adapters; - -import java.lang.reflect.*; -import java.util.Arrays; -import java.util.HashMap; - -public class GenericIceBreaker implements InvocationHandler { - private final Object delegate; - - public GenericIceBreaker(Object delegate) { - this.delegate = delegate; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Method delegateMethod = getDeclaredMethod(delegate.getClass(), method.getName(), method.getParameterTypes()); - - Object delegateMethodReturn = null; - try { - delegateMethodReturn = delegateMethod.invoke(delegate, args); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException("problems invoking " + method.getName()); - } - if (delegateMethodReturn == null) { - return null; - } - - if (method.getReturnType().isArray()) { - if (method.getReturnType().getComponentType().isInterface() - && !method.getReturnType().getComponentType().equals(delegateMethod.getReturnType().getComponentType())) { - - int arrayLength = Array.getLength(delegateMethodReturn); - Object retArray = Array.newInstance(method.getReturnType().getComponentType(), arrayLength); - for (int i = 0; i < arrayLength; i++) { - Array.set(retArray, - i, - proxy( - method.getReturnType().getComponentType(), - Array.get(delegateMethodReturn, i))); - } - - return retArray; - } - } - - if (method.getReturnType().isInterface() - && !method.getReturnType().equals(delegateMethod.getReturnType())) { - return proxy(method.getReturnType(), delegateMethodReturn); - } - - return delegateMethodReturn; - } - - public static T proxy(Class iface, Object delegate) { - return (T) Proxy.newProxyInstance( - iface.getClassLoader(), - new Class[]{iface}, - new GenericIceBreaker(delegate)); - } - - /* ********************************************************************** * - * * - * Method cache * - * * - * ********************************************************************** */ - - private static final HashMap declaredMethodCache = new HashMap<>(); - - private static synchronized Method getDeclaredMethod(Class cls, String name, Class... paramTypes) - throws NoSuchMethodException, SecurityException - { - MethodCacheKey methodCacheKey = new MethodCacheKey(cls, name, paramTypes); - - Method m = declaredMethodCache.get(methodCacheKey); - if (m == null) { - m = cls.getDeclaredMethod(name, paramTypes); - m.setAccessible(true); - declaredMethodCache.put(methodCacheKey, m); - } - return m; - } - - private static class MethodCacheKey { - final Class cls; - final String name; - final Class[] paramTypes; - - public MethodCacheKey(Class cls, String name, Class... paramTypes) { - this.cls = cls; - this.name = name; - this.paramTypes = paramTypes; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof MethodCacheKey)) - return false; - - MethodCacheKey key2 = (MethodCacheKey) obj; - return cls == key2.cls && name.equals(key2.name) && Arrays.equals(paramTypes, key2.paramTypes); - } - - @Override - public int hashCode() { - return cls.hashCode() + name.hashCode() + Arrays.hashCode(paramTypes); - } - } -} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java deleted file mode 100644 index 20969d8b0..000000000 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/HitInfo.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.fxmisc.richtext.j9adapters; - -public interface HitInfo -{ - int getCharIndex(); - boolean isLeading(); -} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java deleted file mode 100644 index 134b40b12..000000000 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/RectBounds.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.fxmisc.richtext.j9adapters; - -public interface RectBounds -{ - float getMinX(); - - float getMaxX(); - - float getHeight(); -} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java deleted file mode 100644 index 1d9e7287f..000000000 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLayout.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.fxmisc.richtext.j9adapters; - -import javafx.scene.shape.PathElement; - -public interface TextLayout -{ - public static final int TYPE_TEXT = 1 << 0; - public static final int TYPE_UNDERLINE = 1 << 1; - - TextLine[] getLines(); - - int getLineIndex(float y); - - int getCharCount(); - - HitInfo getHitInfo(float x, float y); - PathElement[] getCaretShape(int offset, boolean isLeading, float x, float y); - PathElement[] getRange(int start, int end, int type, float x, float y); -} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java b/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java deleted file mode 100644 index 372c84cab..000000000 --- a/richtextfx/src/main/java/org/fxmisc/richtext/j9adapters/TextLine.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.fxmisc.richtext.j9adapters; - -public interface TextLine -{ - int getLength(); - - RectBounds getBounds(); - - int getStart(); -}