All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dflib.jjava.jupyter.kernel.display.Renderer Maven / Gradle / Ivy

The newest version!
package org.dflib.jjava.jupyter.kernel.display;

import org.dflib.jjava.jupyter.kernel.display.mime.MIMEType;
import org.dflib.jjava.jupyter.kernel.util.InheritanceIterator;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A default renderer may be set and maps a group to a specific subtype.
 * 

* A suffix may be mapped to a type. *

* A type has a default mime type (may be a list) and is always also rendered * as text/plain with toString(). *

* A type must also have other supported types. *

* Objects that implement the render interface override their default renders * but in the event that renderAs (or displayAs) is invoked the specified types * override the defaults. */ public class Renderer { private static class RenderFunctionProps { private final RenderFunction function; private final Set supportedTypes; private final Set preferredTypes; public RenderFunctionProps(RenderFunction function, Set supportedTypes, Set preferredTypes) { this.function = function; this.supportedTypes = supportedTypes; this.preferredTypes = preferredTypes; } public RenderFunction getFunction() { return function; } public Set getSupportedTypes() { return supportedTypes; } public Set getPreferredTypes() { return preferredTypes; } } public class RenderRegistration { private final Set supported; private final Set preferred; private final Set> types; public RenderRegistration(Class type) { this.supported = new LinkedHashSet<>(); this.preferred = new LinkedHashSet<>(); this.types = new LinkedHashSet<>(); this.types.add(type); } public RenderRegistration supporting(MIMEType... types) { Collections.addAll(this.supported, types); return this; } public RenderRegistration preferring(MIMEType... types) { supporting(types); Collections.addAll(this.preferred, types); return this; } public RenderRegistration supporting(String... types) { for (String type : types) this.supported.add(MIMEType.parse(type)); return this; } public RenderRegistration preferring(String... types) { supporting(types); for (String type : types) this.preferred.add(MIMEType.parse(type)); return this; } public RenderRegistration onType(Class type) { this.types.add(type); return this; } public void register(RenderFunction function) { Set supported = this.supported.isEmpty() ? DisplayDataRenderable.ANY : this.supported; Set preferred = this.preferred.isEmpty() ? supported : this.preferred; Renderer.this.register(supported, preferred, types, function); } } private final Map> renderFunctions; private final Map suffixMappings; public Renderer() { this.renderFunctions = new HashMap<>(); this.suffixMappings = new HashMap<>(); } public RenderRegistration createRegistration(Class type) { return new RenderRegistration<>(type); } public void register(Set supported, Set preferred, Set> types, RenderFunction function) { RenderFunctionProps props = new RenderFunctionProps(function, supported, preferred); types.forEach(c -> this.renderFunctions.compute(c, (k, v) -> { List functions = v != null ? v : new LinkedList<>(); functions.add(props); return functions; })); } private static DisplayData finalizeDisplayData(DisplayData data, Object value) { if (!data.hasDataForType(MIMEType.TEXT_PLAIN)) data.putText(String.valueOf(value)); return data; } /** * Render the object with the preferred render type. *

* The rendering algorithm is as follows: *

    *
  1. * The object is rendered as {@code text/plain} with {@link String#valueOf(Object)}. *
  2. *
  3. * If the object is {@link DisplayDataRenderable} ask it to render itself as the {@link DisplayDataRenderable#getPreferredRenderTypes() preferred types}. *
  4. *
  5. * Else iterate over the implemented with the {@link InheritanceIterator} until a render function is found. Use this * function to render the object. *
  6. *
* * @param value the object to render. * @param params a map of parameters that render functions may use. * * @return the data container holding the rendered view of the {@code value}. */ @SuppressWarnings("unchecked") public DisplayData render(Object value, Map params) { DisplayData out = new DisplayData(); if (value instanceof DisplayDataRenderable) { DisplayDataRenderable renderable = (DisplayDataRenderable) value; RenderRequestTypes.Builder requestTypes = new RenderRequestTypes.Builder(this.suffixMappings::get); requestTypes.withType(MIMEType.TEXT_PLAIN); renderable.getPreferredRenderTypes().forEach(requestTypes::withType); renderable.render(new RenderContext(requestTypes.build(), this, params, out)); return finalizeDisplayData(out, value); } Iterator inheritedTypes = new InheritanceIterator(value.getClass()); while (inheritedTypes.hasNext()) { Class type = inheritedTypes.next(); List allRenderFunctionProps = this.renderFunctions.get(type); if (allRenderFunctionProps != null && !allRenderFunctionProps.isEmpty()) { for (RenderFunctionProps renderFunctionProps : allRenderFunctionProps) { RenderRequestTypes.Builder requestTypes = new RenderRequestTypes.Builder(this.suffixMappings::get); requestTypes.withType(MIMEType.TEXT_PLAIN); renderFunctionProps.getPreferredTypes().forEach(requestTypes::withType); renderFunctionProps.getFunction().render( value, new RenderContext(requestTypes.build(), this, params, out) ); } return finalizeDisplayData(out, value); } } return finalizeDisplayData(out, value); } /** * A {@link #render(Object, Map)} variant that supplies an empty parameter map. * * @param value the object to render. * * @return a {@link DisplayData} container with all the rendered data. */ public DisplayData render(Object value) { return render(value, new LinkedHashMap<>()); } /** * Render the object as the specified types if possible. *

* The rendering algorithm is as follows: *

    *
  1. * The object is rendered as {@code text/plain} with {@link String#valueOf(Object)} no * matter what types are requested. *
  2. *
  3. * If the object is {@link DisplayDataRenderable} and any of it's {@link DisplayDataRenderable#getSupportedRenderTypes() supported types} * are requested, it is asked to render itself. *
  4. *
  5. * While all of the requested types have not be rendered yet: *
      *
    1. * For every type in the {@link InheritanceIterator}, apply the same scheme as step 2. *
    2. *
    3. * Remove all rendered types from the request. *
    4. *
    *
  6. *
* * @param value the object to render. * @param params a map of parameters that render functions may use. * @param types the {@link MIMEType#parse(String) MIME types} to render the object as. * * @return a {@link DisplayData} container with all the rendered data. */ @SuppressWarnings("unchecked") public DisplayData renderAs(Object value, Map params, String... types) { DisplayData out = new DisplayData(); RenderRequestTypes.Builder builder = new RenderRequestTypes.Builder(this.suffixMappings::get); builder.withType(MIMEType.TEXT_PLAIN); for (String type : types) builder.withType(type); RenderRequestTypes requestTypes = builder.build(); RenderContext context = new RenderContext(requestTypes, this, params, out); if (value instanceof DisplayDataRenderable) { DisplayDataRenderable renderable = (DisplayDataRenderable) value; if (requestTypes.anyRequestedIsSupported(renderable.getSupportedRenderTypes())) { renderable.render(context); requestTypes.removeFulfilledRequests(out); } } Iterator inheritedTypes = new InheritanceIterator(value.getClass()); while (inheritedTypes.hasNext() && !requestTypes.isEmpty()) { Class type = inheritedTypes.next(); List allRenderFunctionProps = this.renderFunctions.get(type); if (allRenderFunctionProps != null) { for (RenderFunctionProps renderFunctionProps : allRenderFunctionProps) { if (requestTypes.anyRequestedIsSupported(renderFunctionProps.getSupportedTypes())) { renderFunctionProps.getFunction().render(value, context); requestTypes.removeFulfilledRequests(out); } } } } return finalizeDisplayData(out, value); } /** * A {@link #renderAs(Object, Map, String...)} variant that supplies an empty parameter map. * * @param value the object to render. * @param types the {@link MIMEType#parse(String) MIME types} to render the object as. * * @return a {@link DisplayData} container with all the rendered data. */ public DisplayData renderAs(Object value, String... types) { return this.renderAs(value, new LinkedHashMap<>(), types); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy