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

org.sfj.DrawDot Maven / Gradle / Ivy

Go to download

This is a collection of disparate pieces of code, each file containing a single piece of functionality. The idea is software minimalism, you get 1000 lines of Java code, no dependencies. Collection of useful things, especially for prototyping/rapid development.

There is a newer version: 1.2.0
Show newest version
/*
 * Copyright 2020 C. Schanck
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.sfj;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Utility class for drawing dotty/graphviz graphs. Tremendous overview here:
 * https://ncona.com/2020/06/create-diagrams-with-code-using-graphviz/
 * on a good working set of things to do.
 * 

This is a work in progress, there is a lot more that can be done than * the API here supports, but there is enough to use it for its intended purpose: * drawing visuals of data structures. */ public class DrawDot { final static String INDENTION = " "; public interface Color { String colorName(); } public enum ColorsSVG implements Color { aliceblue, antiquewhite, aqua, aquamarine, azure, beige, bisque, black, blanchedalmond, blue, blueviolet, brown, burlywood, cadetblue, chartreuse, chocolate, coral, cornflowerblue, cornsilk, crimson, cyan, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen, darkgrey, darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred, darksalmon, darkseagreen, darkslateblue, darkslategray, darkslategrey, darkturquoise, darkviolet, deeppink, deepskyblue, dimgray, dimgrey, dodgerblue, firebrick, floralwhite, forestgreen, fuchsia, gainsboro, ghostwhite, gold, goldenrod, gray, grey, green, greenyellow, honeydew, hotpink, indianred, indigo, ivory, khaki, lavender, lavenderblush, lawngreen, lemonchiffon, lightblue, lightcoral, lightcyan, lightgoldenrodyellow, lightgray, lightgreen, lightgrey, lightpink, lightsalmon, lightseagreen, lightskyblue, lightslategray, lightslategrey, lightsteelblue, lightyellow, lime, limegreen, linen, magenta, maroon, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, mediumvioletred, midnightblue, mintcream, mistyrose, moccasin, navajowhite, navy, oldlace, olive, olivedrab, orange, orangered, orchid, palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip, peachpuff, peru, pink, plum, powderblue, purple, red, rosybrown, royalblue, saddlebrown, salmon, sandybrown, seagreen, seashell, sienna, silver, skyblue, slateblue, slategray, slategrey, snow, springgreen, steelblue, tan, teal, thistle, tomato, turquoise, violet, wheat, white, whitesmoke, yellow, yellowgreen; @Override public String colorName() { return name().toLowerCase(); } } public enum ColorsX11 implements Color { aliceblue, antiquewhite, antiquewhite1, antiquewhite2, antiquewhite3, antiquewhite4, aquamarine, aquamarine1, aquamarine2, aquamarine3, aquamarine4, azure, azure1, azure2, azure3, azure4, beige, bisque, bisque1, bisque2, bisque3, bisque4, black, blanchedalmond, blue, blue1, blue2, blue3, blue4, blueviolet, brown, brown1, brown2, brown3, brown4, burlywood, burlywood1, burlywood2, burlywood3, burlywood4, cadetblue, cadetblue1, cadetblue2, cadetblue3, cadetblue4, chartreuse, chartreuse1, chartreuse2, chartreuse3, chartreuse4, chocolate, chocolate1, chocolate2, chocolate3, chocolate4, coral, coral1, coral2, coral3, coral4, cornflowerblue, cornsilk, cornsilk1, cornsilk2, cornsilk3, cornsilk4, crimson, cyan, cyan1, cyan2, cyan3, cyan4, darkgoldenrod, darkgoldenrod1, darkgoldenrod2, darkgoldenrod3, darkgoldenrod4, darkgreen, darkkhaki, darkolivegreen, darkolivegreen1, darkolivegreen2, darkolivegreen3, darkolivegreen4, darkorange, darkorange1, darkorange2, darkorange3, darkorange4, darkorchid, darkorchid1, darkorchid2, darkorchid3, darkorchid4, darksalmon, darkseagreen, darkseagreen1, darkseagreen2, darkseagreen3, darkseagreen4, darkslateblue, darkslategray, darkslategray1, darkslategray2, darkslategray3, darkslategray4, darkslategrey, darkturquoise, darkviolet, deeppink, deeppink1, deeppink2, deeppink3, deeppink4, deepskyblue, deepskyblue1, deepskyblue2, deepskyblue3, deepskyblue4, dimgray, dimgrey, dodgerblue, dodgerblue1, dodgerblue2, dodgerblue3, dodgerblue4, firebrick, firebrick1, firebrick2, firebrick3, firebrick4, floralwhite, forestgreen, gainsboro, ghostwhite, gold, gold1, gold2, gold3, gold4, goldenrod, goldenrod1, goldenrod2, goldenrod3, goldenrod4, gray, gray0, gray1, gray10, gray100, gray11, gray12, gray13, gray14, gray15, gray16, gray17, gray18, gray19, gray2, gray20, gray21, gray22, gray23, gray24, gray25, gray26, gray27, gray28, gray29, gray3, gray30, gray31, gray32, gray33, gray34, gray35, gray36, gray37, gray38, gray39, gray4, gray40, gray41, gray42, gray43, gray44, gray45, gray46, gray47, gray48, gray49, gray5, gray50, gray51, gray52, gray53, gray54, gray55, gray56, gray57, gray58, gray59, gray6, gray60, gray61, gray62, gray63, gray64, gray65, gray66, gray67, gray68, gray69, gray7, gray70, gray71, gray72, gray73, gray74, gray75, gray76, gray77, gray78, gray79, gray8, gray80, gray81, gray82, gray83, gray84, gray85, gray86, gray87, gray88, gray89, gray9, gray90, gray91, gray92, gray93, gray94, gray95, gray96, gray97, gray98, gray99, green, green1, green2, green3, green4, greenyellow, grey, grey0, grey1, grey10, grey100, grey11, grey12, grey13, grey14, grey15, grey16, grey17, grey18, grey19, grey2, grey20, grey21, grey22, grey23, grey24, grey25, grey26, grey27, grey28, grey29, grey3, grey30, grey31, grey32, grey33, grey34, grey35, grey36, grey37, grey38, grey39, grey4, grey40, grey41, grey42, grey43, grey44, grey45, grey46, grey47, grey48, grey49, grey5, grey50, grey51, grey52, grey53, grey54, grey55, grey56, grey57, grey58, grey59, grey6, grey60, grey61, grey62, grey63, grey64, grey65, grey66, grey67, grey68, grey69, grey7, grey70, grey71, grey72, grey73, grey74, grey75, grey76, grey77, grey78, grey79, grey8, grey80, grey81, grey82, grey83, grey84, grey85, grey86, grey87, grey88, grey89, grey9, grey90, grey91, grey92, grey93, grey94, grey95, grey96, grey97, grey98, grey99, honeydew, honeydew1, honeydew2, honeydew3, honeydew4, hotpink, hotpink1, hotpink2, hotpink3, hotpink4, indianred, indianred1, indianred2, indianred3, indianred4, indigo, invis, ivory, ivory1, ivory2, ivory3, ivory4, khaki, khaki1, khaki2, khaki3, khaki4, lavender, lavenderblush, lavenderblush1, lavenderblush2, lavenderblush3, lavenderblush4, lawngreen, lemonchiffon, lemonchiffon1, lemonchiffon2, lemonchiffon3, lemonchiffon4, lightblue, lightblue1, lightblue2, lightblue3, lightblue4, lightcoral, lightcyan, lightcyan1, lightcyan2, lightcyan3, lightcyan4, lightgoldenrod, lightgoldenrod1, lightgoldenrod2, lightgoldenrod3, lightgoldenrod4, lightgoldenrodyellow, lightgray, lightgrey, lightpink, lightpink1, lightpink2, lightpink3, lightpink4, lightsalmon, lightsalmon1, lightsalmon2, lightsalmon3, lightsalmon4, lightseagreen, lightskyblue, lightskyblue1, lightskyblue2, lightskyblue3, lightskyblue4, lightslateblue, lightslategray, lightslategrey, lightsteelblue, lightsteelblue1, lightsteelblue2, lightsteelblue3, lightsteelblue4, lightyellow, lightyellow1, lightyellow2, lightyellow3, lightyellow4, limegreen, linen, magenta, magenta1, magenta2, magenta3, magenta4, maroon, maroon1, maroon2, maroon3, maroon4, mediumaquamarine, mediumblue, mediumorchid, mediumorchid1, mediumorchid2, mediumorchid3, mediumorchid4, mediumpurple, mediumpurple1, mediumpurple2, mediumpurple3, mediumpurple4, mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, mediumvioletred, midnightblue, mintcream, mistyrose, mistyrose1, mistyrose2, mistyrose3, mistyrose4, moccasin, navajowhite, navajowhite1, navajowhite2, navajowhite3, navajowhite4, navy, navyblue, none, oldlace, olivedrab, olivedrab1, olivedrab2, olivedrab3, olivedrab4, orange, orange1, orange2, orange3, orange4, orangered, orangered1, orangered2, orangered3, orangered4, orchid, orchid1, orchid2, orchid3, orchid4, palegoldenrod, palegreen, palegreen1, palegreen2, palegreen3, palegreen4, paleturquoise, paleturquoise1, paleturquoise2, paleturquoise3, paleturquoise4, palevioletred, palevioletred1, palevioletred2, palevioletred3, palevioletred4, papayawhip, peachpuff, peachpuff1, peachpuff2, peachpuff3, peachpuff4, peru, pink, pink1, pink2, pink3, pink4, plum, plum1, plum2, plum3, plum4, powderblue, purple, purple1, purple2, purple3, purple4, red, red1, red2, red3, red4, rosybrown, rosybrown1, rosybrown2, rosybrown3, rosybrown4, royalblue, royalblue1, royalblue2, royalblue3, royalblue4, saddlebrown, salmon, salmon1, salmon2, salmon3, salmon4, sandybrown, seagreen, seagreen1, seagreen2, seagreen3, seagreen4, seashell, seashell1, seashell2, seashell3, seashell4, sienna, sienna1, sienna2, sienna3, sienna4, skyblue, skyblue1, skyblue2, skyblue3, skyblue4, slateblue, slateblue1, slateblue2, slateblue3, slateblue4, slategray, slategray1, slategray2, slategray3, slategray4, slategrey, snow, snow1, snow2, snow3, snow4, springgreen, springgreen1, springgreen2, springgreen3, springgreen4, steelblue, steelblue1, steelblue2, steelblue3, steelblue4, tan, tan1, tan2, tan3, tan4, thistle, thistle1, thistle2, thistle3, thistle4, tomato, tomato1, tomato2, tomato3, tomato4, transparent, turquoise, turquoise1, turquoise2, turquoise3, turquoise4, violet, violetred, violetred1, violetred2, violetred3, violetred4, wheat, wheat1, wheat2, wheat3, wheat4, white, whitesmoke, yellow, yellow1, yellow2, yellow3, yellow4, yellowgreen; @Override public String colorName() { return name().toLowerCase(); } } public enum EdgeStyle { solid, dashed, dotted, bold, } public enum NodeStyle { solid, dashed, dotted, bold, rounded, diagonals, filled, striped, wedged, } final static Function XFORM_QUOTED = (in) -> '"' + in.toString() + '"'; final static Function XFORM_HTML = (in) -> '<' + in.toString() + '>'; final static Function XFORM_ENUM_LOWERCASE = (in) -> ((Enum) in).name().toLowerCase(); final static Function XFORM_TOSTRING = Objects::toString; public enum RankDir {TB, RL, BT, LR} static class Attribute { final String name; final Object value; final Function xform; public Attribute(String name, Object value, Function xform) { this.name = name; this.value = value; this.xform = xform; } public Attribute(String name, Object value) { this.name = name; this.value = value; this.xform = XFORM_TOSTRING; } String transform() { if (value != null) { return xform.apply(value); } return null; } void emit(List opts) { if (value != null) { opts.add(name + "=" + xform.apply(value)); } } @Override public String toString() { return "Attribute{" + "name='" + name + '\'' + ", value=" + value + '}'; } } interface Attributable { E add(Attribute attr); } public static class BaseAttributable implements Attributable { protected final LinkedHashMap attributes = new LinkedHashMap<>(); @Override @SuppressWarnings("unchecked") public E add(Attribute attr) { attributes.put(attr.name, attr); return (E) this; } protected void emit(List opts) { for (Attribute a : attributes.values()) { a.emit(opts); } } @Override public String toString() { return "Attributable{" + "attributes=" + attributes + '}'; } public Map getAttributes() { return attributes; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } BaseAttributable that = (BaseAttributable) o; return attributes.equals(that.attributes); } @Override public int hashCode() { return Objects.hash(attributes); } } interface Labeled extends Attributable { default E html(String html) { return add(new Attribute("label", html, XFORM_HTML)); } default E label(String label) { return add(new Attribute("label", label, XFORM_QUOTED)); } default E labelFontColor(String color) { return add(new Attribute("fontcolor", color)); } default E labelFontsize(int fontSize) { return add(new Attribute("fontsize", fontSize)); } } @SuppressWarnings("unchecked") interface NodeStyled extends Attributable { default E style(NodeStyle style, Color... colorlist) { style(style); if (colorlist.length > 0) { String clist = Arrays.stream(colorlist).map(Object::toString).collect(Collectors.joining(":")); return add(new Attribute("fillcolor", clist, XFORM_QUOTED)); } return (E) this; } default E style(NodeStyle style) { return add(new Attribute("style", style.name())); } default E style(NodeStyle style, String colorlist) { style(style); return add(new Attribute("fillcolor", colorlist, XFORM_QUOTED)); } } @SuppressWarnings("unchecked") interface EdgeStyled extends Attributable { default E style(EdgeStyle style, Color... colorlist) { style(style); if (colorlist.length > 0) { String clist = Arrays.stream(colorlist).map(Object::toString).collect(Collectors.joining(":")); return add(new Attribute("fillcolor", clist, XFORM_QUOTED)); } return (E) this; } default E style(EdgeStyle style) { return add(new Attribute("style", style.name())); } default E style(EdgeStyle style, String colorlist) { style(style); return add(new Attribute("fillcolor", colorlist, XFORM_QUOTED)); } } interface Colored extends Attributable { default E color(String color) { return add(new Attribute("color", color)); } default E color(Color color) { return add(new Attribute("color", color.colorName())); } } interface Shaped extends Attributable { default E shaped(Shapes shape) { return add(new Attribute("shape", shape, XFORM_ENUM_LOWERCASE)); } } @SuppressWarnings("unchecked") interface Commentable { List getCommentsBefore(); List getCommentsAfter(); default E commentBefore(String... lines) { getCommentsBefore().addAll(Arrays.asList(lines)); return (E) this; } default E commentAfter(String... lines) { getCommentsAfter().addAll(Arrays.asList(lines)); return (E) this; } } public static class Graph implements Child, Commentable { private final String name; private boolean clustered = false; private boolean directed = true; private final ArrayList children = new ArrayList<>(); private final ArrayList connections = new ArrayList<>(); private RankDir rankDir = null; private final List commentsBefore = new LinkedList<>(); private final List commentsAfter = new LinkedList<>(); private final List commentsHeader = new LinkedList<>(); private final List commentsFooter = new LinkedList<>(); public Graph(String name) { this.name = name; } public List getChildren() { return children; } public List getConnections() { return connections; } public Graph clustered(boolean clustered) { this.clustered = clustered; return this; } @Override public List getCommentsBefore() { return commentsBefore; } @Override public List getCommentsAfter() { return commentsAfter; } public Graph commentHeader(String... lines) { this.commentsHeader.addAll(Arrays.asList(lines)); return this; } public Graph commentFooter(String... lines) { this.commentsFooter.addAll(Arrays.asList(lines)); return this; } public Graph directed(boolean directed) { this.directed = directed; return this; } public Graph rankdir(RankDir dir) { this.rankDir = dir; return this; } public String footer() { return "}"; } public String header() { String w = (directed ? "digraph " : "graph "); return w + name + " {"; } @Override public String id() { return name; } @Override public String toString() { return "Graph{" + "name='" + name + '\'' + ", clustered=" + clustered + ", directed=" + directed + '}'; } @Override public void emit(DrawDot dot, PrintWriter writer, String indent) throws IOException { for (String s : commentsBefore) { writer.println(indent + "// " + s); } writer.println(indent + (clustered ? "cluster_" : "") + header()); for (String s : commentsHeader) { writer.println(indent + INDENTION + "// " + s); } if (rankDir != null) { writer.println(indent + INDENTION + "rankdir=\"" + rankDir.name() + '"'); } for (Child child : children) { child.emit(dot, writer, indent + INDENTION); } for (Connection connection : connections) { connection.emit(this, writer, indent + INDENTION); } writer.println(indent + footer()); for (String s : commentsFooter) { writer.println(indent + INDENTION + "// " + s); } for (String s : commentsAfter) { writer.println(indent + "// " + s); } } public Graph add(Child child) { children.add(child); return this; } public Graph add(Connection connection) { connections.add(connection); return this; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Graph graph = (Graph) o; return name.equals(graph.name); } @Override public int hashCode() { return Objects.hash(name); } } public interface Child { void emit(DrawDot dot, PrintWriter writer, String indent) throws IOException; String id(); } public static class Node extends BaseAttributable implements Child, Colored, Labeled, Shaped, NodeStyled, Commentable { private final String id; private final List commentsBefore = new LinkedList<>(); private final List commentsAfter = new LinkedList<>(); public Node(String id) { this.id = id; } @Override public List getCommentsBefore() { return commentsBefore; } @Override public List getCommentsAfter() { return commentsAfter; } Node recordV(String... labels) { shaped(Shapes.RECORD); return label("{" + String.join(" | ", labels) + "}"); } Node recordH(String... labels) { shaped(Shapes.RECORD); return label(String.join(" | ", labels)); } @Override public String id() { return id; } @Override public void emit(DrawDot dot, PrintWriter writer, String indent) { for (String s : commentsBefore) { writer.println(indent + "// " + s); } writer.print(indent); writer.print('"' + id() + "\" "); List opts = new LinkedList<>(); emit(opts); if (!opts.isEmpty()) { writer.print(opts); } writer.println(); for (String s : commentsAfter) { writer.println(indent + "// " + s); } } @Override public String toString() { return "Node{" + "id='" + id + '\'' + "} " + super.toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Node node = (Node) o; return id.equals(node.id); } @Override public int hashCode() { return Objects.hash(id); } } public enum Arrows { BOX, CROW, CURVE, DIAMOND, DOT, ICURVE, INV, NONE, NORMAL, TEE, VEE; Arrow arrow() { return new Arrow(this); } } public static class Arrow extends BaseAttributable implements Colored, Labeled { private final Arrows type; private Arrow(Arrows type) { this.type = type; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Arrow arrow = (Arrow) o; return type == arrow.type; } @Override public int hashCode() { return Objects.hash(type); } } public static class Shape extends BaseAttributable { private final Shapes shape; public Shape() { this.shape = null; } public Shape(Shapes shape) { this.shape = shape; } } public enum Shapes { BOX, POLYGON, ELLIPSE, OVAL, CIRCLE, POINT, EGG, TRIANGLE, PLAINTEXT, PLAIN, DIAMOND, TRAPEZIUM, PARALLELOGRAM, HOUSE, PENTAGON, HEXAGON, SEPTAGON, OCTAGON, DOUBLECIRCLE, DOUBLEOCTAGON, TRIPLEOCTAGON, INVTRIANGLE, INVTRAPEZIUM, INVHOUSE, MDIAMOND, MSQUARE, MCIRCLE, RECT, RECTANGLE, SQUARE, STAR, NONE, UNDERLINE, CYLINDER, NOTE, TAB, FOLDER, BOX3D, COMPONENT, PROMOTER, CDS, TERMINATOR, UTR, PRIMERSITE, RESTRICTIONSITE, FIVEPOVERHANG, THREEPOVERHANG, NOVERHANG, ASSEMBLY, SIGNATURE, INSULATOR, RIBOSITE, RNASTAB, PROTEASESITE, PROTEINSTAB, RPROMOTER, RARROW, LARROW, LPROMOTER, MRECORD, RECORD } public enum ConnectionType { FORWARD, BACK, BOTH, NONE } public static class Connection extends BaseAttributable implements Labeled, Colored, EdgeStyled, Commentable { private final Node from; private final String fromPort; private final Node to; private final String toPort; private Arrow head; private Arrow tail; private final List commentsBefore = new LinkedList<>(); private final List commentsAfter = new LinkedList<>(); public Connection(Node from, Node to) { this(from, null, to, null); } public Connection(Node from, String fromPort, Node to, String toPort) { this.from = Objects.requireNonNull(from); this.fromPort = fromPort; this.to = Objects.requireNonNull(to); this.toPort = toPort; } @Override public List getCommentsBefore() { return commentsBefore; } @Override public List getCommentsAfter() { return commentsAfter; } public Connection penWidth(int penWidth) { add(new Attribute("penwidth", penWidth)); return this; } public Node getFrom() { return from; } public String getFromPort() { return fromPort; } public Node getTo() { return to; } public String getToPort() { return toPort; } public Arrow getHead() { return head; } public Arrow getTail() { return tail; } public Connection connectionType(ConnectionType cType) { add(new Attribute("dir", cType)); return this; } public void emit(Graph parent, PrintWriter writer, String indent) { for (String s : commentsBefore) { writer.println(indent + "// " + s); } writer.print(indent); writer.print('"' + from.id() + '"'); if (fromPort != null) { writer.print(":\"" + fromPort + '"'); } writer.print(parent.directed ? " -> " : " -- "); writer.print('"' + to.id() + '"'); if (toPort != null) { writer.print(":\"" + toPort + '"'); } LinkedList opts = new LinkedList<>(); emit(opts); if (head != null) { head.emit(opts); } if (tail != null) { tail.emit(opts); } if (!opts.isEmpty()) { writer.print(" " + opts); } writer.println(); for (String s : commentsAfter) { writer.println(indent + "// " + s); } } public Connection head(Arrow head) { this.head = head; return this; } public Connection tail(Arrow tail) { this.tail = tail; return this; } @Override public boolean equals(Object o) { return (this == o); } @Override public int hashCode() { return System.identityHashCode(this); } @Override public String toString() { return "Connection{" + "from=" + from + ", to=" + to + ", head=" + head + ", tail=" + tail + "} " + super.toString(); } } private final Graph root; public DrawDot(final String name) { this.root = new Graph(name); } public Graph root() { return root; } public void emit(Writer writer) throws IOException { PrintWriter pw = new PrintWriter(writer); root.emit(this, pw, ""); pw.flush(); } public void emit(PrintWriter writer) throws IOException { root.emit(this, writer, ""); } public static String escapeForDotHTML(String str) { boolean initted = false; StringBuilder sb = null; for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); switch (ch) { case '&': if (!initted) { sb = new StringBuilder(); sb.append(str, 0, i); initted = true; } sb.append("&"); break; case '"': if (!initted) { sb = new StringBuilder(); sb.append(str, 0, i); initted = true; } sb.append("""); break; case '<': if (!initted) { sb = new StringBuilder(); sb.append(str, 0, i); initted = true; } sb.append("<"); break; case '>': if (!initted) { sb = new StringBuilder(); sb.append(str, 0, i); initted = true; } sb.append(">"); break; default: if (initted) { sb.append(ch); } break; } } if (initted) { return sb.toString(); } return str; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy