package org.codefx.libfx.dom; import java.net.MalformedURLException; import java.net.URL; import java.util.Objects; import java.util.Optional; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent.EventType; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.events.Event; /** * Creates a {@link HyperlinkEvent} from the DOM-{@link Event} specified during construction. * <p> * In does the actual work for the {@link DomEventConverter} but is a "one-shot" version in the sense that it can only * convert the event specified during construction. */ class SingleDomEventConverter { /** * The DOM-{@link Event} from which the {@link HyperlinkEvent} will be created. */ private final Event domEvent; /** * The source of the {@link #domEvent}. */ private final Object source; /** * Creates a new converter for the specified arguments. * * @param domEvent * the DOM-{@link Event} from which the {@link HyperlinkEvent} will be created * @param source * the source of the {@code domEvent} */ public SingleDomEventConverter(Event domEvent, Object source) { Objects.requireNonNull(domEvent, "The argument 'domEvent' must not be null."); Objects.requireNonNull(source, "The argument 'source' must not be null."); this.domEvent = domEvent; this.source = source; } // #begin CONVERT /** * Indicates whether the DOM event specified during construction can be converted to a {@link HyperlinkEvent}. * * @return true if the event's {@link Event#getType() type} has an equivalent {@link EventType EventType} */ public boolean canConvert() { Optional<EventType> eventType = getEventTypeFrom(domEvent); return eventType.isPresent(); } /** * Returns the hyperlink event type equivalent of the specified DOM event if it exists. * * @param domEvent * the DOM-{@link Event} whose {@link Event#getType() type} will be determined * @return the equivalent Hyperlink.{@link EventType} if it exists */ private static Optional<EventType> getEventTypeFrom(Event domEvent) { String domEventName = domEvent.getType(); Optional<EventType> eventType = DomEventType .byName(domEventName) .flatMap(domEventType -> domEventType.toHyperlinkEventType()); return eventType; } /** * Converts the event specified during construction to a hyperlink event. * * @return a {@link HyperlinkEvent} * @throws IllegalArgumentException * if the specified event can not be converted to a hyperlink event; this is the case if * {@link #canConvert()} returns false */ public HyperlinkEvent convert() throws IllegalArgumentException { EventType type = getEventTypeForDomEvent(); Optional<URL> url = getURL(); String linkDescription = getTextContent(); return new HyperlinkEvent(source, type, url.orElse(null), linkDescription); } /** * Returns the hyperlink event type equivalent of the specified DOM event. * * @return the equivalent Hyperlink.{@link EventType} * @throws IllegalArgumentException * if the {@link #domEvent}'s type has no equivalent hyperlink event type */ private EventType getEventTypeForDomEvent() throws IllegalArgumentException { Optional<EventType> eventType = getEventTypeFrom(domEvent); if (eventType.isPresent()) return eventType.get(); else throw new IllegalArgumentException( "The DOM event '" + domEvent + "' of type '" + domEvent.getType() + "' can not be converted to a hyperlink event."); } /** * Returns the {@link #domEvent}'s target's text content. * * @return the description */ private String getTextContent() { Element targetElement = (Element) domEvent.getTarget(); return targetElement.getTextContent(); } /** * Returns the URL the interacted hyperlink points to. * * @return the {@link URL} if it could be created */ private Optional<URL> getURL() { Element targetElement = (Element) domEvent.getTarget(); Element anchor = getAnchor(targetElement); Optional<String> baseURI = Optional.ofNullable(anchor.getBaseURI()); String href = anchor.getAttribute("href"); return createURL(baseURI, href); } /** * Returns the same element if it is an anchor (in the sense of HTML, i.e. has the a-tag). If it is not the closest * parent which is an anchor is returned. If no such parent exists, an {@link IllegalArgumentException} is thrown. * * @param domElement * the {@link Element} on which the search for an anchor element starts * @return an {@link Element} which is an anchor * @throws IllegalArgumentException * if neither the specified element nor one of its parents is an anchor */ private static Element getAnchor(Element domElement) throws IllegalArgumentException { Optional<Element> anchor = getAnchorAncestor(Optional.of(domElement)); return anchor.orElseThrow(() -> new IllegalArgumentException( "Neither the event's target element nor one of its parent nodes is an anchor.")); } /** * Searches for an a-tag starting on the specified node and recursing to its ancestors. * * @param domNode * the node which is checked for the a-tag * @return an {@link Optional} containing an anchor if one was found; otherwise an empty {@code Optional} */ private static Optional<Element> getAnchorAncestor(Optional<Node> domNode) { // if there is no node, there can be no anchor, so return empty if (!domNode.isPresent()) return Optional.empty(); Node node = domNode.get(); // only elements can be anchors, so if the node is no element, recurse to its parent boolean nodeIsNoElement = !(node instanceof Element); if (nodeIsNoElement) return getAnchorAncestor(Optional.ofNullable(node.getParentNode())); // if the node is an element, it might be an anchor Element element = (Element) node; boolean isAnchor = element.getTagName().equalsIgnoreCase("a"); if (isAnchor) return Optional.of(element); // if the element is no anchor, recurse to its parent return getAnchorAncestor(Optional.ofNullable(element.getParentNode())); } /** * Creates a URL from the specified base URI and href of the link which caused the event. * * @param baseURI * the base URI of the anchor {@link Element} which caused the event * @param href * the href attribute value of the {@link Element} which caused the event * @return a URL if one could be created */ private static Optional<URL> createURL(Optional<String> baseURI, String href) { // create URL context from the document's base URI URL context = null; try { if (baseURI.isPresent()) context = new URL(baseURI.get()); } catch (MalformedURLException e) { // if LibFX supports logging, this could be logged: // "Could not create a URL context from the base URI \"" + baseURI + "\".", e } // create URL from context and href try { URL url = new URL(context, href); return Optional.of(url); } catch (MalformedURLException e) { // if LibFX supports logging, this could be logged: // "Could not create a URL from href \"" + href + "\" and context \"" + context + "\"." // until then return empty } return Optional.empty(); } // #end CONVERT }