├── .github
├── FUNDING.yml
└── workflows
│ └── jekyll-gh-pages.yml
├── .gitignore
├── LICENSE
├── Readme.md
├── details.html
├── htmlflow.uml
├── pom.xml
└── src
├── main
├── java
│ └── htmlflow
│ │ ├── HtmlDoc.java
│ │ ├── HtmlFlow.java
│ │ ├── HtmlPage.java
│ │ ├── HtmlTemplate.java
│ │ ├── HtmlView.java
│ │ ├── HtmlViewAsync.java
│ │ ├── continuations
│ │ ├── HtmlContinuation.java
│ │ ├── HtmlContinuationAsync.java
│ │ ├── HtmlContinuationAsyncTerminationNode.java
│ │ ├── HtmlContinuationSync.java
│ │ ├── HtmlContinuationSyncCloseAndIndent.java
│ │ ├── HtmlContinuationSyncDynamic.java
│ │ └── HtmlContinuationSyncStatic.java
│ │ ├── exceptions
│ │ └── HtmlFlowAppendException.java
│ │ ├── flowifier
│ │ ├── AbstractHtmlToJavaHtmlFlowNodeVisitor.java
│ │ ├── DefaultHtmlToJavaHtmlFlowNodeVisitor.java
│ │ ├── Flowifier.java
│ │ └── HtmlToJavaHtmlFlowNodeVisitor.java
│ │ └── visitor
│ │ ├── HtmlDocVisitor.java
│ │ ├── HtmlViewVisitor.java
│ │ ├── HtmlViewVisitorAsync.java
│ │ ├── HtmlVisitor.java
│ │ ├── Indentation.java
│ │ ├── PreprocessingVisitor.java
│ │ ├── PreprocessingVisitorAsync.java
│ │ └── Tags.java
├── kotlin
│ └── htmlflow
│ │ ├── HtmlFlowExtensions.kt
│ │ ├── HtmlFlowSuspending.kt
│ │ ├── HtmlViewSuspend.kt
│ │ ├── HtmlViewVisitorSuspend.kt
│ │ ├── PreprocessingVisitorSuspend.kt
│ │ └── continuations
│ │ ├── HtmlContinuationSuspendable.kt
│ │ ├── HtmlContinuationSuspendableAsync.kt
│ │ ├── HtmlContinuationSuspendableSync.kt
│ │ ├── HtmlContinuationSuspendableSyncCloseAndIndent.kt
│ │ ├── HtmlContinuationSuspendableSyncDynamic.kt
│ │ ├── HtmlContinuationSuspendableSyncStatic.kt
│ │ ├── HtmlContinuationSuspendableTerminationNode.kt
│ │ └── HtmlContinuationSuspending.kt
└── resources
│ └── templates
│ └── HtmlView-Header.txt
└── test
├── java
└── htmlflow
│ ├── exceptions
│ └── HtmlFlowAppendExceptionTest.java
│ ├── flowifier
│ └── test
│ │ └── TestFlowifier.java
│ ├── test
│ ├── TestAsyncView.java
│ ├── TestAsyncViewInConcurInMultpleThreads.java
│ ├── TestAsyncViewInConcurrency.java
│ ├── TestAttributesClassId.java
│ ├── TestAwaitDynamic.java
│ ├── TestBooleanAttributes.java
│ ├── TestCustomElements.java
│ ├── TestDivDetails.java
│ ├── TestDivDetailsInConcurrency.java
│ ├── TestDynamicCases.java
│ ├── TestDynamicVersusOf.java
│ ├── TestDynamicVersusOfWithPartials.java
│ ├── TestEscapeText.java
│ ├── TestFormAttributes.java
│ ├── TestHtmlViewAsElement.java
│ ├── TestIndentation.java
│ ├── TestNewScriptTypes.java
│ ├── TestPartialsToPrintStream.java
│ ├── TestReactiveView.java
│ ├── TestResourceNotFound.java
│ ├── TestTable.java
│ ├── TestVoidElements.java
│ ├── TestWrongUseOfViews.java
│ ├── Utils.java
│ ├── model
│ │ ├── AsyncDynamicModel.java
│ │ ├── AsyncModel.java
│ │ ├── Priority.java
│ │ ├── Status.java
│ │ ├── Stock.java
│ │ ├── Student.java
│ │ ├── StudentWithGrade.java
│ │ ├── Task.java
│ │ ├── TaskDelayed.java
│ │ └── Track.java
│ └── views
│ │ ├── HtmlCustomElements.java
│ │ ├── HtmlDynamic.java
│ │ ├── HtmlDynamicChainTwiceOnTopgenius.java
│ │ ├── HtmlDynamicStocks.java
│ │ ├── HtmlForReadme.java
│ │ ├── HtmlLists.java
│ │ ├── HtmlPartials.java
│ │ ├── HtmlTables.java
│ │ ├── HtmlVoidElements.java
│ │ └── HtmlWithoutIndentation.java
│ └── visitor
│ └── TagsTest.java
├── kotlin
└── htmlflow
│ └── test
│ ├── TestKotlinExtensions.kt
│ ├── TestKotlinExtensionsOnPartials.kt
│ ├── TestKotlinSuspend.kt
│ ├── TestSuspendableView.kt
│ ├── TestSuspendableViewArtist.kt
│ ├── TestSuspendableViewInConcurInMultpleThreads.kt
│ ├── TestSuspendableViewInConcurrency.kt
│ └── TestSuspendableViewWeather.kt
└── resources
├── TaskList.html
├── asyncTest.html
├── asyncTestSecond.html
├── asyncTestWithDynamic.html
├── customElements.html
├── htmlWithoutIndentationBodyDivP.html
├── htmlWithoutIndentationBodyPre.html
├── htmlflowSample05ForFlowifier.java
├── logging.properties
├── nestedTable.html
├── partialViewHeader.html
├── stocks3items.html
├── stocks3others.html
├── stocks5items.html
├── task3.html
├── task4.html
├── task5.html
├── task9.html
├── testAddPartialAndCheckParentPrintStream.html
├── testIdAndClassAttribute.html
├── topgeniusBadAndSit.html
├── topgeniusSpaceAndPressure.html
└── voidElements.html
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: fmcarvalho
4 | patreon: # Replace with a single Patreon username
5 | open_collective: htmlflow
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/workflows/jekyll-gh-pages.yml:
--------------------------------------------------------------------------------
1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages
2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["gh-pages"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Build job
26 | build:
27 | runs-on: ubuntu-latest
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@v3
31 | with:
32 | ref: gh-pages
33 | - name: Setup Pages
34 | uses: actions/configure-pages@v3
35 | - name: Build with Jekyll
36 | uses: actions/jekyll-build-pages@v1
37 | with:
38 | source: ./
39 | destination: ./_site
40 | - name: Upload artifact
41 | uses: actions/upload-pages-artifact@v2
42 |
43 | # Deployment job
44 | deploy:
45 | environment:
46 | name: github-pages
47 | url: ${{ steps.deployment.outputs.page_url }}
48 | runs-on: ubuntu-latest
49 | needs: build
50 | steps:
51 | - name: Deploy to GitHub Pages
52 | id: deployment
53 | uses: actions/deploy-pages@v2
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | /bin/
3 |
4 | # InteliJ
5 | .idea
6 | *.iml
7 |
8 | # Eclipse Core
9 | .project
10 |
11 | # JDT-specific (Eclipse Java Development Tools)
12 | .classpath
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Miguel Gamboa (gamboa.pt)
4 | Copyright (c) Luís Duarte (https://github.com/lcduarte)
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/details.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Typesafe is awesome! :-)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/htmlflow.uml:
--------------------------------------------------------------------------------
1 |
2 |
3 | JAVA
4 | htmlflow
5 |
6 | org.xmlet.htmlapifaster.Element
7 | htmlflow.HtmlView
8 | htmlflow.HtmlVisitorPrintStream
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Methods
21 |
22 | All
23 | private
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/HtmlDoc.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow;
26 |
27 | import htmlflow.visitor.HtmlDocVisitor;
28 | import org.xmlet.htmlapifaster.Html;
29 |
30 | /**
31 | * Static Html view.
32 | *
33 | * @author Miguel Gamboa, Luís Duare
34 | */
35 | public class HtmlDoc extends HtmlPage {
36 |
37 | private final HtmlDocVisitor visitor;
38 |
39 | HtmlDoc(HtmlDocVisitor visitor) {
40 | this.visitor = visitor;
41 | }
42 |
43 | public final Html html() {
44 | this.getVisitor().write(HEADER);
45 | return new Html<>(this);
46 | }
47 |
48 | @Override
49 | public String getName() {
50 | return "HtmlDoc";
51 | }
52 |
53 | @Override
54 | public HtmlPage setIndented(boolean isIndented) {
55 | return new HtmlDoc(getVisitor().clone(isIndented));
56 | }
57 |
58 | @Override
59 | public HtmlPage threadSafe() {
60 | throw new IllegalStateException("HtmlDoc is not reusable and does not keep internal static blocks!" +
61 | "Thus it does not require thread safety!");
62 | }
63 |
64 | @Override
65 | public HtmlDocVisitor getVisitor() {
66 | return visitor;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/HtmlPage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow;
26 |
27 | import org.xmlet.htmlapifaster.Div;
28 | import org.xmlet.htmlapifaster.Element;
29 | import org.xmlet.htmlapifaster.Html;
30 | import org.xmlet.htmlapifaster.Tr;
31 |
32 | import java.io.BufferedReader;
33 | import java.io.FileNotFoundException;
34 | import java.io.IOException;
35 | import java.io.InputStream;
36 | import java.io.InputStreamReader;
37 | import java.io.UncheckedIOException;
38 | import java.net.URL;
39 |
40 | import static java.util.stream.Collectors.joining;
41 |
42 | /**
43 | * The root container for HTML elements.
44 | * It is responsible for managing the {@code org.xmlet.htmlapi.ElementVisitor}
45 | * implementation, which is responsible for printing the tree of elements and
46 | * attributes.
47 | *
48 | * Instances of HtmlPage are immutable. Any change to its properties returns a new
49 | * instance of HtmlPage.
50 | *
51 | * @author Miguel Gamboa, Luís Duare
52 | * created on 29-03-2012
53 | */
54 | public abstract class HtmlPage implements Element> {
55 |
56 | public static final String HEADER;
57 | private static final String NEWLINE = System.getProperty("line.separator");
58 | private static final String HEADER_TEMPLATE = "templates/HtmlView-Header.txt";
59 |
60 | static {
61 | try {
62 | URL headerUrl = HtmlPage.class
63 | .getClassLoader()
64 | .getResource(HEADER_TEMPLATE);
65 | if(headerUrl == null)
66 | throw new FileNotFoundException(HEADER_TEMPLATE);
67 | InputStream headerStream = headerUrl.openStream();
68 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(headerStream))) {
69 | HEADER = reader.lines().collect(joining(NEWLINE));
70 | }
71 | } catch (IOException e) {
72 | throw new UncheckedIOException(e);
73 | }
74 | }
75 |
76 | public abstract Html html();
77 |
78 | public final Div div() {
79 | return new Div<>(this);
80 | }
81 |
82 | public final Tr tr() {
83 | return new Tr<>(this);
84 | }
85 |
86 | /**
87 | * Returns a new instance of HtmlFlow with the same properties of this object
88 | * but with indented set to the value of isIndented parameter.
89 | */
90 | public abstract HtmlPage setIndented(boolean isIndented);
91 |
92 | @Override
93 | public final HtmlPage self() {
94 | return this;
95 | }
96 |
97 | public abstract HtmlPage threadSafe();
98 |
99 | @Override
100 | public Element __() {
101 | throw new IllegalStateException(getName() + " is the root of Html tree and it has not any parent.");
102 | }
103 |
104 | @Override
105 | public Element getParent() {
106 | throw new IllegalStateException(getName() + " is the root of Html tree and it has not any parent.");
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/HtmlTemplate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow;
26 |
27 | /**
28 | * The model of type T is obtained from view's render() method
29 | * and passed to dynamic blocks.
30 | */
31 | @FunctionalInterface
32 | public interface HtmlTemplate {
33 |
34 | void resolve(HtmlPage page);
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/HtmlView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow;
26 |
27 | import htmlflow.visitor.HtmlVisitor;
28 | import org.xmlet.htmlapifaster.Html;
29 |
30 | import java.util.function.Supplier;
31 |
32 | /**
33 | * Dynamic views can be bound to a Model object.
34 | *
35 | * @param Type of the model rendered with this view.
36 | *
37 | * @author Miguel Gamboa, Luís Duare
38 | */
39 | public class HtmlView extends HtmlPage {
40 | /**
41 | * Function that consumes an HtmlView to produce HTML elements.
42 | */
43 | private final HtmlTemplate template;
44 | /**
45 | * This field is like a union with the threadLocalVisitor, being used alternatively.
46 | * For non thread safe scenarios Visitors maybe shared concurrently by multiple threads.
47 | * On the other-hand, in thread-safe scenarios each thread must have its own visitor to
48 | * emit HTML to the output, and we use the threadLocalVisitor field instead.
49 | */
50 | private final HtmlVisitor visitor;
51 | /**
52 | * This issue is regarding ThreadLocal variables that are supposed to be garbage collected.
53 | * The given example deals with a static field of ThreadLocal which persists beyond an instance.
54 | * In this case the ThreadLocal is hold in an instance field and should stay with all
55 | * thread local instances during its entire life cycle.
56 | */
57 | @java.lang.SuppressWarnings("squid:S5164")
58 | private final ThreadLocal threadLocalVisitor;
59 | protected final Supplier visitorSupplier;
60 | private final boolean threadSafe;
61 | /**
62 | * Auxiliary constructor used by clone().
63 | */
64 | HtmlView(
65 | Supplier visitorSupplier,
66 | HtmlTemplate template,
67 | boolean threadSafe)
68 | {
69 | this.visitorSupplier = visitorSupplier;
70 | this.template = template;
71 | this.threadSafe = threadSafe;
72 | if(threadSafe) {
73 | this.visitor = null;
74 | this.threadLocalVisitor = ThreadLocal.withInitial(visitorSupplier);
75 | } else {
76 | this.visitor = visitorSupplier.get();
77 | this.threadLocalVisitor = null;
78 | }
79 | }
80 |
81 | public final Html html() {
82 | this.getVisitor().write(HEADER);
83 | return new Html<>(this);
84 | }
85 |
86 | public HtmlView threadSafe(){
87 | return clone(visitorSupplier, true);
88 | }
89 |
90 | @Override
91 | public HtmlVisitor getVisitor() {
92 | return threadSafe
93 | ? threadLocalVisitor.get()
94 | : visitor;
95 | }
96 |
97 | public HtmlView setOut(Appendable out) {
98 | getVisitor().setAppendable(out);
99 | return this;
100 | }
101 |
102 | @Override
103 | public String getName() {
104 | return "HtmlView";
105 | }
106 |
107 | public String render() {
108 | return render(null);
109 | }
110 |
111 | public String render(M model) {
112 | StringBuilder str = ((StringBuilder) getVisitor().out());
113 | str.setLength(0);
114 | getVisitor().resolve(model);
115 | return str.toString();
116 | }
117 |
118 | public void write(M model) {
119 | getVisitor().resolve(model);
120 | }
121 |
122 | public void write() {
123 | write(null);
124 | }
125 |
126 | /**
127 | * Since HtmlView is immutable this is the preferred way to create a copy of the
128 | * existing HtmlView instance with a different threadSafe state.
129 | *
130 | * @param visitorSupplier
131 | * @param threadSafe
132 | */
133 | protected final HtmlView clone(
134 | Supplier visitorSupplier,
135 | boolean threadSafe)
136 | {
137 | return new HtmlView<>(visitorSupplier, template, threadSafe);
138 | }
139 |
140 | /**
141 | * Returns a new instance of HtmlFlow with the same properties of this object
142 | * but with indented set to the value of isIndented parameter.
143 | */
144 | @Override
145 | public final HtmlView setIndented(boolean isIndented) {
146 | return HtmlFlow.view(getVisitor().out(), template, isIndented, threadSafe);
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/HtmlViewAsync.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-22, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow;
26 |
27 | import htmlflow.visitor.HtmlViewVisitorAsync;
28 | import org.xmlet.htmlapifaster.Html;
29 |
30 | import java.util.concurrent.CompletableFuture;
31 |
32 | /**
33 | * Asynchronous views can be bound to a domain object with an asynchronous API.
34 | *
35 | * @param Type of the model rendered with this view.
36 | *
37 | * @author Pedro Fialho
38 | */
39 | public class HtmlViewAsync extends HtmlPage {
40 | /**
41 | * Function that consumes an HtmlView to produce HTML elements.
42 | */
43 | private final HtmlTemplate template;
44 |
45 | private final HtmlViewVisitorAsync visitor;
46 |
47 | private final boolean threadSafe;
48 |
49 | HtmlViewAsync(HtmlViewVisitorAsync visitor, HtmlTemplate template) {
50 | this(visitor, template, true);
51 | }
52 |
53 | public HtmlViewAsync(HtmlViewVisitorAsync visitor, HtmlTemplate template, boolean safe) {
54 | this.visitor = visitor;
55 | this.template = template;
56 | this.threadSafe = safe;
57 | }
58 |
59 | @Override
60 | public final Html html() {
61 | visitor.write(HEADER);
62 | return new Html<>(this);
63 | }
64 |
65 | @Override
66 | public HtmlViewAsync setIndented(boolean isIndented) {
67 | return HtmlFlow.viewAsync(template, isIndented, threadSafe);
68 | }
69 |
70 | @Override
71 | public HtmlViewVisitorAsync getVisitor() {
72 | return visitor;
73 | }
74 |
75 | @Override
76 | public String getName() {
77 | return "HtmlViewAsync";
78 | }
79 |
80 | @Override
81 | public HtmlViewAsync threadSafe(){
82 | return new HtmlViewAsync<>(visitor, template);
83 | }
84 |
85 | public HtmlViewAsync threadUnsafe(){
86 | return new HtmlViewAsync<>(visitor, template, false);
87 | }
88 |
89 |
90 | public final CompletableFuture writeAsync(Appendable out, M model) {
91 | if (threadSafe) {
92 | return visitor.clone(out).finishedAsync(model);
93 | }
94 | visitor.setAppendable(out);
95 | return visitor.finishedAsync(model);
96 | }
97 |
98 | public final CompletableFuture renderAsync() {
99 | return renderAsync(null);
100 | }
101 |
102 | public final CompletableFuture renderAsync(M model) {
103 | StringBuilder str = new StringBuilder();
104 | return writeAsync(str, model).thenApply( nothing -> str.toString());
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/continuations/HtmlContinuation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-2022, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | * and Pedro Fialho.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in all
15 | * copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | * SOFTWARE.
24 | */
25 | package htmlflow.continuations;
26 |
27 | import htmlflow.visitor.HtmlVisitor;
28 | import org.xmlet.htmlapifaster.Element;
29 | import org.xmlet.htmlapifaster.ElementVisitor;
30 |
31 | import java.lang.reflect.Constructor;
32 | import java.lang.reflect.InvocationTargetException;
33 |
34 | /**
35 | * Base class for a linked list of nodes, corresponding to HtmlContinuation objects.
36 | * HtmlContinuation is responsible for emitting an HTML block and call the next node.
37 | */
38 | public abstract class HtmlContinuation extends HtmlContinuationSuspendable {
39 | /**
40 | * A negative number means that should be ignored.
41 | */
42 | final int currentDepth;
43 | /**
44 | * In case of the middle of a beginning tag, it tells if it is closed, or not,
45 | * i.e. if it is {@code ""} or it is {@code " E copyElement(E element, HtmlVisitor v) {
86 | try {
87 | Constructor ctor = ((Class) element
88 | .getClass())
89 | .getDeclaredConstructor(Element.class, ElementVisitor.class, boolean.class);
90 | ctor.setAccessible(true);
91 | return ctor.newInstance(element.getParent(), v, false); // false to not dispatch visit now
92 | } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException ex) {
93 | throw new IllegalStateException(ex);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/continuations/HtmlContinuationAsync.java:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations;
2 |
3 | import htmlflow.visitor.HtmlVisitor;
4 | import org.xmlet.htmlapifaster.Element;
5 | import org.xmlet.htmlapifaster.async.AwaitConsumer;
6 |
7 | /**
8 | * HtmlContinuation for an asynchronous block (i.e. AwaitConsumer) depending of an asynchronous object model.
9 | * The next continuation will be invoked on completion of asynchronous object model.
10 | *
11 | * @param the type of the parent HTML element received by the dynamic HTML block.
12 | * @param the type of the template's model.
13 | */
14 | public class HtmlContinuationAsync extends HtmlContinuation {
15 |
16 | final E element;
17 | final AwaitConsumer consumer;
18 |
19 | public HtmlContinuationAsync(int currentDepth,
20 | boolean isClosed,
21 | E element,
22 | AwaitConsumer consumer,
23 | HtmlVisitor visitor,
24 | HtmlContinuation next) {
25 | super(currentDepth, isClosed, visitor, next);
26 | this.element = element;
27 | this.consumer = consumer;
28 | }
29 |
30 | @Override
31 | public final void execute(Object model) {
32 | if (currentDepth >= 0) {
33 | this.visitor.setIsClosed(isClosed);
34 | this.visitor.setDepth(currentDepth);
35 | }
36 | this.consumer.accept(element, (T) model, () -> {
37 | if (next != null) {
38 | next.execute(model);
39 | }
40 | });
41 | }
42 |
43 | @Override
44 | public HtmlContinuation copy(HtmlVisitor v) {
45 | return new HtmlContinuationAsync<>(
46 | currentDepth,
47 | isClosed,
48 | copyElement(element, v),
49 | consumer,
50 | v,
51 | next != null ? next.copy(v) : null); // call copy recursively
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/continuations/HtmlContinuationAsyncTerminationNode.java:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations;
2 |
3 | import htmlflow.visitor.HtmlVisitor;
4 |
5 | import java.util.concurrent.CompletableFuture;
6 |
7 | /**
8 | * @author Pedro Fialho
9 | **/
10 | public class HtmlContinuationAsyncTerminationNode extends HtmlContinuation {
11 |
12 | private final CompletableFuture cf;
13 |
14 | public HtmlContinuationAsyncTerminationNode(CompletableFuture cf) {
15 | super(-1, false, null, null);
16 | this.cf = cf;
17 | }
18 |
19 | public void execute(Object model) {
20 | cf.complete(null);
21 | }
22 |
23 | @Override
24 | public HtmlContinuationAsyncTerminationNode copy(HtmlVisitor visitor) {
25 | throw new UnsupportedOperationException("Used once and never copied!");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/continuations/HtmlContinuationSync.java:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations;
2 |
3 | import htmlflow.visitor.HtmlVisitor;
4 |
5 | public abstract class HtmlContinuationSync extends HtmlContinuation{
6 | /**
7 | * @param currentDepth Indentation depth associated to this block.
8 | * @param isClosed
9 | * @param visitor
10 | * @param next
11 | */
12 | protected HtmlContinuationSync(int currentDepth, boolean isClosed, HtmlVisitor visitor, HtmlContinuation next) {
13 | super(currentDepth, isClosed, visitor, next);
14 | }
15 | /**
16 | * Executes this continuation and calls the next one if exist.
17 | *
18 | * @param model
19 | */
20 | @Override
21 | public final void execute(Object model) {
22 | if (currentDepth >= 0) {
23 | this.visitor.setIsClosed(isClosed);
24 | this.visitor.setDepth(currentDepth);
25 | }
26 | emitHtml(model);
27 | if (next != null) {
28 | next.execute(model);
29 | }
30 | }
31 | /**
32 | * Hook method to emit HTML.
33 | *
34 | * @param model
35 | */
36 | protected abstract void emitHtml(Object model);
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/continuations/HtmlContinuationSyncCloseAndIndent.java:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations;
2 |
3 | import htmlflow.visitor.HtmlVisitor;
4 |
5 | /**
6 | * @author Pedro Fialho
7 | **/
8 | public class HtmlContinuationSyncCloseAndIndent extends HtmlContinuationSync {
9 | /**
10 | * Sets indentation to -1 to inform that visitor should continue with previous indentation.
11 | * The isClosed is useless here.
12 | */
13 | public HtmlContinuationSyncCloseAndIndent(HtmlVisitor visitor, HtmlContinuation next) {
14 | super(-1, false, visitor, next); // The isClosed parameter is useless in this case
15 | }
16 | public HtmlContinuationSyncCloseAndIndent(HtmlVisitor visitor) {
17 | super(-1, false, visitor, null); // The isClosed parameter is useless in this case
18 | }
19 |
20 | @Override
21 | protected final void emitHtml(Object model) {
22 | /**
23 | * !!!!! This continuation may follow a dynamic or await block that may create a block or inline element.
24 | * Block elements increment (depth) indentation at the beginning and decrement at the end.
25 | * On the other hand, inline elements keep indentation (depth) unmodified.
26 | * We assume most dynamic or await blocks will create block elements.
27 | * Thus, here we start by decrementing depth.
28 | * However, this could not be true for some cases, and we will get inconsistent indentation !!!!
29 | */
30 | visitor.setDepth(visitor.getDepth() - 1);
31 | visitor.newlineAndIndent();
32 | }
33 |
34 | @Override
35 | public HtmlContinuation copy(HtmlVisitor visitor) {
36 | return new HtmlContinuationSyncCloseAndIndent(
37 | visitor,
38 | next != null ? next.copy(visitor) : null); // call copy recursively
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/continuations/HtmlContinuationSyncDynamic.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-2022, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | * and Pedro Fialho.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in all
15 | * copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | * SOFTWARE.
24 | */
25 |
26 | package htmlflow.continuations;
27 |
28 | import htmlflow.visitor.HtmlVisitor;
29 | import org.xmlet.htmlapifaster.Element;
30 |
31 | import java.util.function.BiConsumer;
32 |
33 | /**
34 | * HtmlContinuation for a dynamic block (i.e. BiConsumer) depending of an object model.
35 | *
36 | * @param the type of the parent HTML element received by the dynamic HTML block.
37 | * @param the type of the template's model.
38 | */
39 | public class HtmlContinuationSyncDynamic extends HtmlContinuationSync {
40 |
41 | /**
42 | * The continuation that consumes the element and a model.
43 | */
44 | final BiConsumer consumer;
45 | /**
46 | * The element passed to the continuation consumer.
47 | */
48 | final E element;
49 |
50 | /**
51 | * @param currentDepth Indentation depth associated to this block.
52 | * @param consumer The continuation that consumes the element and a model.
53 | */
54 | public HtmlContinuationSyncDynamic(
55 | int currentDepth,
56 | boolean isClosed,
57 | E element, BiConsumer consumer,
58 | HtmlVisitor visitor,
59 | HtmlContinuation next
60 | ) {
61 | super(currentDepth, isClosed, visitor, next);
62 | this.element = element;
63 | this.consumer = consumer;
64 | }
65 | @Override
66 | protected final void emitHtml(Object model) {
67 | consumer.accept(element, (T) model);
68 | }
69 |
70 | @Override
71 | public HtmlContinuation copy(HtmlVisitor v) {
72 | return new HtmlContinuationSyncDynamic<>(
73 | currentDepth,
74 | isClosed,
75 | copyElement(element, v),
76 | consumer,
77 | v,
78 | next != null ? next.copy(v) : null); // call copy recursively
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/continuations/HtmlContinuationSyncStatic.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-2022, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | * and Pedro Fialho.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in all
15 | * copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | * SOFTWARE.
24 | */
25 |
26 | package htmlflow.continuations;
27 |
28 | import htmlflow.visitor.HtmlVisitor;
29 |
30 | /**
31 | * HtmlContinuation for a static HTML block.
32 | */
33 | public class HtmlContinuationSyncStatic extends HtmlContinuationSync {
34 | final String staticHtmlBlock;
35 | /**
36 | * Sets indentation to -1 to inform that visitor should continue with previous indentation.
37 | * The isClosed is useless because it just writes what it is in its staticHtmlBlock.
38 | */
39 | public HtmlContinuationSyncStatic(String staticHtmlBlock, HtmlVisitor visitor, HtmlContinuation next) {
40 | super(-1, false, visitor, next); // The isClosed parameter is useless in this case of Static HTML block.
41 | this.staticHtmlBlock = staticHtmlBlock.intern(); // Maybe intern() here is useless and implicit by VM
42 | }
43 |
44 | @Override
45 | protected final void emitHtml(Object model) {
46 | visitor.write(staticHtmlBlock);
47 | }
48 |
49 | @Override
50 | public HtmlContinuation copy(HtmlVisitor v) {
51 | return new HtmlContinuationSyncStatic(
52 | staticHtmlBlock,
53 | v,
54 | next != null ? next.copy(v) : null); // call copy recursively
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/exceptions/HtmlFlowAppendException.java:
--------------------------------------------------------------------------------
1 | package htmlflow.exceptions;
2 |
3 | import java.util.function.UnaryOperator;
4 |
5 | /**
6 | * @author Pedro Fialho
7 | *
8 | * Class to be used where is an error while writing to the output
9 | **/
10 | public class HtmlFlowAppendException extends RuntimeException {
11 |
12 | private static final UnaryOperator EXCEPTION_MESSAGE = msg -> "There has been an exception in writing the Html" +
13 | ", underlying exception is "+msg;
14 |
15 | public HtmlFlowAppendException(Throwable th) {
16 | super(EXCEPTION_MESSAGE.apply(th.getMessage()), th);
17 | }
18 |
19 | public HtmlFlowAppendException(String message) {
20 | super(EXCEPTION_MESSAGE.apply(message));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/flowifier/DefaultHtmlToJavaHtmlFlowNodeVisitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-19 HtmlFlow.org
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package htmlflow.flowifier;
25 |
26 | /**
27 | * Default implementation of the visitor of a JSoup node that converts the HTML
28 | * source code into a Java class, it uses a StringBuilder to append the content
29 | * of the Java class. Note that this implementation is not intended to be
30 | * thread-safe
31 | *
32 | * @author Julien Gouesse
33 | *
34 | */
35 | public class DefaultHtmlToJavaHtmlFlowNodeVisitor extends AbstractHtmlToJavaHtmlFlowNodeVisitor {
36 |
37 | /**
38 | * Constructor with indentation disabled
39 | */
40 | public DefaultHtmlToJavaHtmlFlowNodeVisitor() {
41 | this(false);
42 | }
43 |
44 | /**
45 | * Constructor
46 | *
47 | * @param indented
48 | * true if the generated HTML source code is
49 | * indented, otherwise false
50 | */
51 | public DefaultHtmlToJavaHtmlFlowNodeVisitor(final boolean indented) {
52 | super(StringBuilder::new, indented);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/flowifier/HtmlToJavaHtmlFlowNodeVisitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-19 HtmlFlow.org
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package htmlflow.flowifier;
25 |
26 | import java.io.IOException;
27 | import java.lang.reflect.Method;
28 |
29 | import org.jsoup.nodes.Attribute;
30 | import org.jsoup.nodes.Node;
31 | import org.jsoup.select.NodeVisitor;
32 |
33 | /**
34 | * Visitor of a JSoup node that converts the HTML source code into a Java class
35 | *
36 | * @author Julien Gouesse
37 | *
38 | * @param
39 | * the type of appendable used to store the Java source code
40 | */
41 | public interface HtmlToJavaHtmlFlowNodeVisitor extends NodeVisitor {
42 |
43 | /**
44 | * Appends the header of the Java class, i.e the imports, the declaration of
45 | * the class, the declaration of the method, ...
46 | *
47 | * @throws IOException
48 | * thrown when something wrong occurs while appending the Java
49 | * source code
50 | */
51 | void appendHeader() throws IOException;
52 |
53 | /**
54 | * Appends the footer of the Java class, i.e closes the method and the class
55 | *
56 | * @throws IOException
57 | * thrown when something wrong occurs while appending the Java
58 | * source code
59 | */
60 | void appendFooter() throws IOException;
61 |
62 | /**
63 | * Appends the attribute value and key
64 | *
65 | * @param attribute
66 | * the attribute
67 | * @param nodeClass
68 | * the class of the node
69 | * @throws IOException
70 | * thrown when something wrong occurs while appending the Java
71 | * source code
72 | */
73 | void appendAttribute(Attribute attribute, Class> nodeClass) throws IOException;
74 |
75 | /**
76 | * Tells whether a JSoup node cannot be closed
77 | *
78 | * @param node
79 | * the JSoup node
80 | * @return true if the JSoup node cannot be closed, otherwise
81 | * false
82 | */
83 | boolean isUncloseable(Node node);
84 |
85 | /**
86 | * Converts the content of a Java string into a string that can be declared
87 | * in a Java class passed as a method parameter
88 | *
89 | * @param javaStringContent
90 | * the content of a Java string
91 | * @return a string that can be declared in a Java class passed as a method
92 | * parameter
93 | */
94 | String convertJavaStringContentToJavaDeclarableString(String javaStringContent);
95 |
96 | /**
97 | * Returns the appendable used to store the Java class
98 | *
99 | * @return the appendable used to store the Java class
100 | */
101 | T getAppendable();
102 |
103 | /**
104 | * Returns the class of the node whose name is passed if any, otherwise
105 | * null
106 | *
107 | * @param nodeName
108 | * the node name
109 | * @return the class of the node whose name is passed if any, otherwise
110 | * null
111 | */
112 | Class> getClassFromNodeName(String nodeName);
113 |
114 | /**
115 | * Returns the method of the class node that matches with the attribute if
116 | * any, otherwise null
117 | *
118 | * @param nodeClass
119 | * the class of the node
120 | * @param attribute
121 | * the attribute
122 | * @return the method of the class node that matches with the attribute if
123 | * any, otherwise null
124 | */
125 | Method getMethodFromAttribute(Class> nodeClass, Attribute attribute);
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/visitor/HtmlDocVisitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow.visitor;
26 |
27 | import org.xmlet.htmlapifaster.Element;
28 | import org.xmlet.htmlapifaster.SuspendConsumer;
29 | import org.xmlet.htmlapifaster.async.AwaitConsumer;
30 |
31 | import java.util.function.BiConsumer;
32 |
33 | /**
34 | * This is the implementation of the ElementVisitor (from HtmlApiFaster
35 | * library) that emits HTML immediately with no optimizations.
36 | *
37 | * @author Miguel Gamboa
38 | * created on 04-08-2022
39 | */
40 | public class HtmlDocVisitor extends HtmlVisitor {
41 |
42 | public HtmlDocVisitor(Appendable out, boolean isIndented) {
43 | this(out, isIndented, 0);
44 | }
45 |
46 | public HtmlDocVisitor(Appendable out,boolean isIndented, int depth) {
47 | super(out, isIndented);
48 | this.out = out;
49 | this.depth = depth;
50 | }
51 |
52 | @Override
53 | public final void visitDynamic(E element, BiConsumer dynamicHtmlBlock) {
54 | throw new IllegalStateException("Wrong use of dynamic() in a static view! Use HtmlView to produce a dynamic view.");
55 | }
56 |
57 | @Override
58 | public final void visitAwait(E element, AwaitConsumer asyncAction) {
59 | throw new IllegalStateException("Wrong use of async() in a static view! Use HtmlViewAsync to produce an async view.");
60 | }
61 |
62 | @Override
63 | public void visitSuspending(E element, SuspendConsumer suspendAction) {
64 | throw new IllegalStateException("Wrong use of suspending() in a static view! Use HtmlViewSuspend to produce an async view.");
65 | }
66 |
67 | @Override
68 | public void resolve(Object model) {
69 | throw new UnsupportedOperationException("HTML has been already emitted on elements flow. " +
70 | "resolve() is only available for HtmlView pages.");
71 | }
72 |
73 | @Override
74 | public final HtmlDocVisitor clone(boolean isIndented) {
75 | return new HtmlDocVisitor(this.out, isIndented);
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/visitor/HtmlViewVisitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-22, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow.visitor;
26 |
27 | import htmlflow.continuations.HtmlContinuation;
28 | import org.xmlet.htmlapifaster.Element;
29 | import org.xmlet.htmlapifaster.SuspendConsumer;
30 | import org.xmlet.htmlapifaster.async.AwaitConsumer;
31 |
32 | import java.util.function.BiConsumer;
33 |
34 |
35 | /**
36 | * This is the base implementation of the ElementVisitor (from HtmlApiFaster library).
37 | *
38 | * HtmlViewVisitor is also a head of HtmlContinuation objects chain, storing all
39 | * needed information to emit HTML corresponding to Static HTML blocks and dynamic HTML.
40 | *
41 | * @author Miguel Gamboa, Luís Duare, Pedro Fialho
42 | * created on 17-01-2018
43 | */
44 | public class HtmlViewVisitor extends HtmlVisitor {
45 | /**
46 | * The first node to be processed.
47 | */
48 | public final HtmlContinuation first;
49 |
50 | public HtmlViewVisitor(Appendable out, boolean isIndented, HtmlContinuation first) {
51 | super(out, isIndented);
52 | this.first = first.copy(this);
53 | }
54 |
55 | /**
56 | * Processing output through invocation of HtmlContinuation objects chain.
57 | */
58 | @Override
59 | public final void resolve(Object model) {
60 | first.execute(model);
61 | }
62 |
63 | @Override
64 | public HtmlVisitor clone(boolean isIndented) {
65 | return new HtmlViewVisitor(out, isIndented, first);
66 | }
67 |
68 | /**
69 | * We only allow a single call to visitDynamic when we are preprocessing the template function
70 | * and building an invocation chain of HtmlContinuation objects (see PreprocessingVisitor).
71 | *
72 | * @param element The parent Element.
73 | * @param dynamicHtmlBlock The continuation that consumes the element and a model.
74 | */
75 | @Override
76 | public final void visitDynamic(E element, BiConsumer dynamicHtmlBlock) {
77 | /**
78 | * After first resolution we will only invoke the dynamicHtmlBlock consumer and no more visits
79 | * to dynamic can happen.
80 | * Otherwise, maybe we are invoking a dynamic nested in other dynamic, which is not allowed!
81 | */
82 | throw new IllegalStateException("You are already in a dynamic block! Do not use dynamic() chained inside another dynamic!");
83 | }
84 |
85 | @Override
86 | public void visitAwait(E element, AwaitConsumer asyncAction) {
87 | throw new IllegalStateException("Wrong use of await() in a HtmlView! Use HtmlFlow.viewAsync() to produce an async view.");
88 | }
89 |
90 | @Override
91 | public void visitSuspending(E element, SuspendConsumer suspendAction) {
92 | throw new IllegalStateException("Wrong use of suspending() in a HtmlView! Use HtmlFlow.viewSuspend() to produce a suspendable view.");
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/visitor/HtmlViewVisitorAsync.java:
--------------------------------------------------------------------------------
1 | package htmlflow.visitor;
2 |
3 | import htmlflow.continuations.HtmlContinuation;
4 | import htmlflow.continuations.HtmlContinuationAsyncTerminationNode;
5 |
6 | import java.util.concurrent.CompletableFuture;
7 |
8 | import static htmlflow.visitor.PreprocessingVisitor.HtmlContinuationSetter.setNext;
9 |
10 |
11 | /**
12 | * @author Pedro Fialho
13 | **/
14 | public class HtmlViewVisitorAsync extends HtmlViewVisitor {
15 |
16 | /**
17 | * The last node to be processed.
18 | */
19 | protected final HtmlContinuation last;
20 |
21 | public HtmlViewVisitorAsync(boolean isIndented, HtmlContinuation first) {
22 | /**
23 | * Intentionally pass null to out Appendable.
24 | * Since this visitor allows concurrency, due to its asynchronous nature, then we must
25 | * clone it on each resolution (i.e. finishAsync()) to avoid sharing continuations across
26 | * different tasks and set a new out Appendable.
27 | */
28 | this(null, isIndented, first);
29 | }
30 |
31 | /**
32 | * Auxiliary for clone.
33 | */
34 | private HtmlViewVisitorAsync(Appendable out, boolean isIndented, HtmlContinuation copy) {
35 | super(out, isIndented, copy);
36 | this.last = findLast();
37 | }
38 |
39 | @Override
40 | public HtmlViewVisitorAsync clone(boolean isIndented) {
41 | return new HtmlViewVisitorAsync(out, isIndented, first);
42 | }
43 |
44 | public HtmlViewVisitorAsync clone(Appendable out) {
45 | return new HtmlViewVisitorAsync(out, isIndented, first);
46 | }
47 |
48 | public CompletableFuture finishedAsync(Object model) {
49 | CompletableFuture cf = new CompletableFuture<>();
50 | HtmlContinuationAsyncTerminationNode terminationNode = new HtmlContinuationAsyncTerminationNode(cf);
51 | /**
52 | * Chain terminationNode next to the last node.
53 | * Keep last pointing to the same node to replace the terminationNode on
54 | * others render async.
55 | */
56 | setNext(last, terminationNode);
57 | /**
58 | * Initializes render on first node.
59 | */
60 | this.first.execute(model);
61 |
62 | return cf;
63 | }
64 |
65 | private HtmlContinuation findLast() {
66 | HtmlContinuation node = this.first;
67 |
68 | while (node.next != null){
69 | node = node.next;
70 | }
71 |
72 | return node;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/visitor/Indentation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2014-18, mcarvalho (gamboa.pt) and lcduarte (github.com/lcduarte)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | package htmlflow.visitor;
26 |
27 |
28 | import static htmlflow.visitor.Tags.FINISH_TAG;
29 |
30 | /**
31 | * @author Luís Duarte created on 9-09-2018
32 | */
33 | class Indentation {
34 |
35 | private static final int MAX_TABS = 1000;
36 | private static final char NEWLINE = '\n';
37 | private static final char TAB = '\t';
38 | private static final String[] tabs = createTabs(MAX_TABS);
39 | private static final String[] closedTabs = createClosedTabs(MAX_TABS);
40 |
41 | private Indentation(){ }
42 |
43 | private static String[] createTabs(int tabsMax){
44 | String[] tabs = new String[tabsMax];
45 |
46 | for (int i = 0; i < tabsMax; i++) {
47 | char[] newTab = new char[i + 1];
48 | newTab[0] = NEWLINE;
49 |
50 | tabs[i] = new String(newTab).replace('\0', TAB);
51 | }
52 |
53 | return tabs;
54 | }
55 |
56 | private static String[] createClosedTabs(int tabsMax){
57 | String[] closedTabs = new String[tabsMax];
58 |
59 | for (int i = 0; i < tabsMax; i++) {
60 |
61 | char[] newClosedTab = new char[i + 2];
62 | newClosedTab[0] = FINISH_TAG;
63 | newClosedTab[1] = NEWLINE;
64 |
65 | closedTabs[i] = new String(newClosedTab).replace('\0', TAB);
66 | }
67 |
68 | return closedTabs;
69 | }
70 |
71 | public static String tabs(int depth){
72 | return tabs[depth];
73 | }
74 |
75 | public static String closedTabs(int depth) {
76 | return closedTabs[depth];
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/visitor/PreprocessingVisitorAsync.java:
--------------------------------------------------------------------------------
1 | package htmlflow.visitor;
2 |
3 | import htmlflow.continuations.HtmlContinuation;
4 | import htmlflow.continuations.HtmlContinuationAsync;
5 | import htmlflow.continuations.HtmlContinuationSyncCloseAndIndent;
6 | import org.xmlet.htmlapifaster.Element;
7 | import org.xmlet.htmlapifaster.SuspendConsumer;
8 | import org.xmlet.htmlapifaster.async.AwaitConsumer;
9 |
10 |
11 | /**
12 | * @author Pedro Fialho
13 | **/
14 | public class PreprocessingVisitorAsync extends PreprocessingVisitor {
15 |
16 | public PreprocessingVisitorAsync(boolean isIndented) {
17 | super(isIndented);
18 | }
19 |
20 | @Override
21 | public void visitAwait(E element, AwaitConsumer asyncHtmlBlock) {
22 | /**
23 | * Creates an HtmlContinuation for the async block.
24 | */
25 | HtmlContinuation asyncCont = new HtmlContinuationAsync<>(
26 | depth,
27 | isClosed,
28 | element,
29 | asyncHtmlBlock,
30 | this,
31 | new HtmlContinuationSyncCloseAndIndent(this));
32 | /**
33 | * We are resolving this view for the first time.
34 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML,
35 | * which will be followed by the asyncCont.
36 | */
37 | chainContinuationStatic(asyncCont);
38 | /**
39 | * We have to run newlineAndIndent to leave isClosed and indentation correct for
40 | * the next static HTML block.
41 | */
42 | indentAndAdvanceStaticBlockIndex();
43 | }
44 |
45 | @Override
46 | public void visitSuspending(E element, SuspendConsumer suspendAction) {
47 | throw new UnsupportedOperationException("Illegal use of suspending builder. To use it you should create a HtmlViewSuspend.");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/htmlflow/visitor/Tags.java:
--------------------------------------------------------------------------------
1 | package htmlflow.visitor;
2 |
3 | import htmlflow.exceptions.HtmlFlowAppendException;
4 |
5 | import java.io.IOException;
6 |
7 | public class Tags {
8 | static final char BEGIN_TAG = '<';
9 | static final String BEGIN_CLOSE_TAG = "";
10 | static final String BEGIN_COMMENT_TAG = "";
12 | static final String ATTRIBUTE_MID = "=\"";
13 | static final char FINISH_TAG = '>';
14 | static final char SPACE = ' ';
15 | static final char QUOTATION = '"';
16 |
17 | private Tags() {
18 | }
19 | /**
20 | * Write {@code ""}.
33 | */
34 | public static void endTag(Appendable out, String elementName) {
35 | try {
36 | out.append(BEGIN_CLOSE_TAG); //
37 | out.append(elementName); //
39 | } catch (IOException e) {
40 | throw new HtmlFlowAppendException(e);
41 | }
42 | }
43 |
44 | /**
45 | * Writes {@code "attributeName=attributeValue"}
46 | */
47 | public static void addAttribute(Appendable out, String attributeName, String attributeValue) {
48 | try {
49 | out.append(SPACE);
50 | out.append(attributeName);
51 | out.append(ATTRIBUTE_MID);
52 | out.append(attributeValue);
53 | out.append(QUOTATION);
54 | }catch (IOException e) {
55 | throw new HtmlFlowAppendException(e);
56 | }
57 | }
58 |
59 | /**
60 | * Writes {@code ""}
61 | */
62 | public static void addComment(Appendable out, String comment) {
63 | try {
64 | out.append(BEGIN_COMMENT_TAG); //
67 | } catch (IOException e) {
68 | throw new HtmlFlowAppendException(e);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/HtmlFlowExtensions.kt:
--------------------------------------------------------------------------------
1 | package htmlflow
2 |
3 | //import jdk.internal.org.jline.utils.ShutdownHooks
4 | import htmlflow.continuations.HtmlContinuationSuspendableTerminationNode
5 | import htmlflow.visitor.HtmlVisitor
6 | import htmlflow.visitor.PreprocessingVisitor
7 | import org.xmlet.htmlapifaster.Element
8 | import org.xmlet.htmlapifaster.Html
9 | import org.xmlet.htmlapifaster.Text
10 |
11 | /**
12 | * Alternative close tag function for `__()`.
13 | */
14 | inline val , Z : Element<*,*>> T.l: Z
15 | get() = this.`__`()
16 |
17 | /**
18 | * Root property of HTML element.
19 | */
20 | inline val HtmlPage.html: Html
21 | get() {
22 | (this.visitor as HtmlVisitor).write(HtmlPage.HEADER)
23 | return Html(self())
24 | }
25 |
26 | /**
27 | * Root builder of HTML element with lambda with receiver.
28 | */
29 | inline fun HtmlPage.html(block : Html.() -> Unit) : HtmlPage {
30 | (this.visitor as HtmlVisitor).write(HtmlPage.HEADER)
31 | return Html(self()).also { it.block() }.l
32 | }
33 |
34 | /**
35 | * Text node property.
36 | */
37 | inline var , Z : Element<*,*>> T.text : T
38 | get() = self()
39 | set(value) {
40 | visitor.visitText(
41 | Text(
42 | self(),
43 | visitor,
44 | value,
45 | )
46 | )
47 | }
48 |
49 | /**
50 | * @param T type of the Element receiver
51 | * @param Z type of the parent Element of the receiver
52 | * @param M type of the model (aka context object)
53 | */
54 | inline fun , Z : Element<*,*>, M> Element.dyn(crossinline cons: T.(M) -> Unit): T {
55 | this.dynamic { elem, model ->
56 | elem.cons(model)
57 | }
58 | return this.self()
59 | }
60 |
61 | /**
62 | * @param T type of the Element receiver
63 | * @param Z type of the parent Element of the receiver
64 | * @param M type of the model (aka context object)
65 | */
66 | inline fun , Z : Element<*,*>, M> Element.await(crossinline cons: T.(M, () -> Unit) -> Unit): T {
67 | this.await { elem, model, cb ->
68 | elem.cons(model) { cb.finish() }
69 | }
70 | return this.self()
71 | }
72 |
73 | /**
74 | * Appendable extension to build an HtmlDoc.
75 | */
76 | fun Appendable.doc(block: HtmlDoc.() -> Unit): HtmlDoc = HtmlFlow.doc(this).also(block)
77 |
78 | /**
79 | * @param M type of the model (aka context object)
80 | */
81 | fun view(template: HtmlPage.() -> Unit) = HtmlFlow.view(template)
82 |
83 | /**
84 | * @param M type of the model (aka context object)
85 | */
86 | fun viewAsync(template: HtmlPage.() -> Unit) = HtmlFlow.viewAsync(template)
87 |
88 | /**
89 | * @param M type of the model (aka context object)
90 | */
91 | fun viewSuspend(template: HtmlPage.() -> Unit) = viewSuspend(
92 | template,
93 | isIndented = true,
94 | threadSafe = false
95 | )
96 |
97 | fun viewSuspend(template: HtmlPage.() -> Unit, isIndented: Boolean, threadSafe: Boolean): HtmlViewSuspend {
98 | val pre = preprocessingSuspend(template, isIndented)
99 | val visitor = HtmlViewVisitorSuspend(
100 | isIndented = isIndented,
101 | first = pre.first
102 | )
103 | /**
104 | * Chain terminationNode next to the last node.
105 | */
106 | val terminationNode = HtmlContinuationSuspendableTerminationNode()
107 | PreprocessingVisitor.HtmlContinuationSetter.setNext(visitor.findLast(), terminationNode)
108 | return HtmlViewSuspend(template, visitor, threadSafe)
109 | }
110 |
111 | /**
112 | * @param template An HtmlTemplate function, which depends on an HtmlView used to create HTMl elements.
113 | * @param isIndented Set indentation on or off.
114 | */
115 | private fun preprocessingSuspend(template: HtmlTemplate, isIndented: Boolean): PreprocessingVisitorSuspend {
116 | val pre = PreprocessingVisitorSuspend(isIndented)
117 | val preView: HtmlView<*> = HtmlView({ pre }, template, false)
118 | template.resolve(preView) // ????? Why ?????
119 | /**
120 | * NO problem with null model. We are just preprocessing static HTML blocks.
121 | * Thus, dynamic blocks which depend on model are not invoked.
122 | */
123 | preView.visitor.resolve(null)
124 | return pre
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/HtmlFlowSuspending.kt:
--------------------------------------------------------------------------------
1 | package htmlflow
2 |
3 | import org.xmlet.htmlapifaster.Element
4 | import org.xmlet.htmlapifaster.SuspendConsumer
5 |
6 | /**
7 | * @param E This HTML element
8 | */
9 | suspend fun > E.suspending(block: suspend E.() -> Unit): E {
10 | if(lookForRootElement() !is HtmlDoc) {
11 | throw IllegalStateException("Illegal use of suspending. Using .suspending { elem -> ...} without a model, is only valid in HtmlDoc.")
12 | }
13 | block(this)
14 | return this
15 | }
16 |
17 | /**
18 | * @param E This HTML element
19 | * @param M The type of model.
20 | */
21 | fun , M> E.suspending(block: suspend E.(M) -> Unit) : E {
22 | /*
23 | * This verification is made in preprocessing using a dummy HtmlView for both sync and async cases.
24 | */
25 | if(lookForRootElement() is HtmlDoc) {
26 | throw IllegalStateException("Illegal use of suspending in HtmlDoc. Using .suspending { elem, model -> ...} is only valid in HtmlViewAsync.")
27 | }
28 |
29 | this.visitor.visitSuspending(this, object : SuspendConsumer {
30 | override suspend fun E.accept(model: M) {
31 | block(this, model as M)
32 | }
33 | })
34 | return this
35 | }
36 |
37 | fun > E.lookForRootElement() : HtmlPage {
38 | if(this is HtmlPage) {
39 | return this
40 | }
41 | return this.parent.lookForRootElement()
42 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/HtmlViewSuspend.kt:
--------------------------------------------------------------------------------
1 | package htmlflow
2 |
3 | import org.xmlet.htmlapifaster.Html
4 |
5 | /**
6 | * Suspendable views can be bound to a domain object with suspending functions based API.
7 | *
8 | * @param Type of the model rendered with this view.
9 | *
10 | * @author Miguel Gamboa
11 | */
12 | class HtmlViewSuspend(
13 | /**
14 | * Function that consumes an HtmlPage to produce HTML elements.
15 | */
16 | private val template: HtmlPage.() -> Unit,
17 | private val visitor: HtmlViewVisitorSuspend,
18 | private var threadSafe: Boolean = true
19 | ) : HtmlPage() {
20 | override fun html(): Html {
21 | visitor.write(HEADER)
22 | return Html(this)
23 | }
24 |
25 | override fun setIndented(isIndented: Boolean): HtmlViewSuspend? {
26 | return viewSuspend(template, isIndented, threadSafe)
27 | }
28 |
29 | override fun getVisitor(): HtmlViewVisitorSuspend {
30 | return visitor
31 | }
32 |
33 | override fun getName(): String {
34 | return "HtmlViewSuspend"
35 | }
36 |
37 | override fun threadSafe(): HtmlViewSuspend {
38 | return HtmlViewSuspend(template, visitor)
39 | }
40 |
41 | fun threadUnsafe(): HtmlViewSuspend {
42 | return HtmlViewSuspend(template, visitor, false)
43 | }
44 |
45 |
46 | suspend fun write(out: Appendable, model: M?) {
47 | if (threadSafe) {
48 | visitor.clone(out).first.executeSuspending(model)
49 | } else {
50 | visitor.setAppendable(out)
51 | visitor.first.executeSuspending(model)
52 | }
53 | }
54 |
55 | suspend fun render(): String = render(null)
56 |
57 | suspend fun render(model: M?) = StringBuilder().let {
58 | write(it, model)
59 | it.toString()
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/HtmlViewVisitorSuspend.kt:
--------------------------------------------------------------------------------
1 | package htmlflow;
2 |
3 | import htmlflow.continuations.HtmlContinuation
4 | import htmlflow.continuations.HtmlContinuationSuspendableTerminationNode
5 | import htmlflow.visitor.HtmlViewVisitor
6 | import htmlflow.visitor.PreprocessingVisitor.HtmlContinuationSetter
7 |
8 | /**
9 | * Intentionally pass null to out Appendable.
10 | * Since this visitor allows concurrency, due to its asynchronous nature, then we must
11 | * clone it on each resolution (i.e. finishAsync()) to avoid sharing continuations across
12 | * different tasks and set a new out Appendable.
13 | */
14 | class HtmlViewVisitorSuspend(
15 | out: Appendable? = null,
16 | isIndented: Boolean,
17 | first: HtmlContinuation?
18 | ) : HtmlViewVisitor (
19 | out, isIndented, first
20 | ) {
21 | /**
22 | * The last node to be processed.
23 | */
24 | private var last: HtmlContinuation? = null
25 |
26 | init {
27 | last = findLast()
28 | }
29 |
30 | override fun clone(isIndented: Boolean): HtmlViewVisitorSuspend {
31 | return HtmlViewVisitorSuspend(out, isIndented, first)
32 | }
33 |
34 | fun clone(out: Appendable): HtmlViewVisitorSuspend {
35 | return HtmlViewVisitorSuspend(out, isIndented, first)
36 | }
37 |
38 | fun findLast(): HtmlContinuation? {
39 | var node = first
40 | while (node.next != null) {
41 | node = node.next
42 | }
43 | return node
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/PreprocessingVisitorSuspend.kt:
--------------------------------------------------------------------------------
1 | package htmlflow
2 |
3 | import htmlflow.continuations.*
4 | import htmlflow.visitor.PreprocessingVisitorAsync
5 | import org.xmlet.htmlapifaster.Element
6 | import org.xmlet.htmlapifaster.SuspendConsumer
7 | import org.xmlet.htmlapifaster.async.AwaitConsumer
8 | import java.util.function.BiConsumer
9 |
10 | class PreprocessingVisitorSuspend(isIndented: Boolean) : PreprocessingVisitorAsync(isIndented) {
11 |
12 | /**
13 | * Here we are creating 2 HtmlContinuation objects: one for previous static HTML and a next one
14 | * corresponding to the consumer passed to dynamic().
15 | * We will first create the dynamic continuation that will be the next node of the static continuation.
16 | *
17 | * U is the type of the model passed to the dynamic HTML block that is the same as T in this visitor.
18 | * Yet, since it came from HtmlApiFaster that is not typed by the Model, then we have to use
19 | * another generic argument for the type of the model.
20 | *
21 | * @param element The parent element.
22 | * @param dynamicHtmlBlock The continuation that consumes the element and a model.
23 | * @param Type of the parent Element.
24 | * @param Type of the model passed to the dynamic HTML block that is the same as T in this visitor.
25 | */
26 | override fun , U> visitDynamic(element: E, dynamicHtmlBlock: BiConsumer) {
27 | /**
28 | * Creates an HtmlContinuation for the dynamic block.
29 | */
30 | val dynamicCont: HtmlContinuation = HtmlContinuationSuspendableSyncDynamic(
31 | depth,
32 | isClosed,
33 | element,
34 | dynamicHtmlBlock,
35 | this,
36 | HtmlContinuationSuspendableSyncCloseAndIndent(this)
37 | )
38 | /**
39 | * We are resolving this view for the first time.
40 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML,
41 | * which will be followed by the dynamicCont.
42 | */
43 | chainContinuationSuspendableStatic(dynamicCont)
44 | /**
45 | * We have to run newlineAndIndent to leave isClosed and indentation correct for
46 | * the next static HTML block.
47 | */
48 | indentAndAdvanceStaticBlockIndex()
49 | }
50 |
51 |
52 | override fun > visitAwait(element: E, asyncHtmlBlock: AwaitConsumer) {
53 | /**
54 | * Creates an HtmlContinuation for a suspending block.
55 | */
56 | val asyncCont: HtmlContinuation = HtmlContinuationSuspendableAsync(
57 | depth,
58 | isClosed,
59 | element,
60 | asyncHtmlBlock,
61 | this,
62 | HtmlContinuationSuspendableSyncCloseAndIndent(this)
63 | )
64 | /**
65 | * We are resolving this view for the first time.
66 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML,
67 | * which will be followed by the suspCont.
68 | */
69 | chainContinuationSuspendableStatic(asyncCont)
70 | /**
71 | * We have to run newlineAndIndent to leave isClosed and indentation correct for
72 | * the next static HTML block.
73 | */
74 | indentAndAdvanceStaticBlockIndex()
75 | }
76 |
77 |
78 | override fun > visitSuspending(element: E, suspendAction: SuspendConsumer) {
79 | /**
80 | * Creates an HtmlContinuation for a suspending block.
81 | */
82 | val suspCont: HtmlContinuation = HtmlContinuationSuspending(
83 | depth,
84 | isClosed,
85 | element,
86 | suspendAction,
87 | this,
88 | HtmlContinuationSuspendableSyncCloseAndIndent(this)
89 | )
90 | /**
91 | * We are resolving this view for the first time.
92 | * Now we just need to create an HtmlContinuation corresponding to the previous static HTML,
93 | * which will be followed by the suspCont.
94 | */
95 | chainContinuationSuspendableStatic(suspCont)
96 | /**
97 | * We have to run newlineAndIndent to leave isClosed and indentation correct for
98 | * the next static HTML block.
99 | */
100 | indentAndAdvanceStaticBlockIndex()
101 | }
102 |
103 | /**
104 | * Creates the last static HTML block.
105 | */
106 | override fun resolve(model: Any?) {
107 | val staticHtml = sb().substring(staticBlockIndex)
108 | val staticCont: HtmlContinuation = HtmlContinuationSuspendableSyncStatic(staticHtml.trim { it <= ' ' }, this, null)
109 | last = if (first == null) staticCont.also {
110 | first = it // assign both first and last
111 | } else HtmlContinuationSetter.setNext(
112 | last,
113 | staticCont
114 | ) // append new staticCont and return it to be the new last continuation.
115 | }
116 |
117 | private fun chainContinuationSuspendableStatic(nextContinuation: HtmlContinuation) {
118 | val staticHtml = sb().substring(staticBlockIndex)
119 | val staticHtmlTrimmed = staticHtml.trim { it <= ' ' } // trim to remove the indentation from static block
120 | val staticCont: HtmlContinuation = HtmlContinuationSuspendableSyncStatic(staticHtmlTrimmed, this, nextContinuation)
121 | if (first == null) first = staticCont // on first visit initializes the first pointer
122 | else HtmlContinuationSetter.setNext(last, staticCont) // else append the staticCont to existing chain
123 | last = nextContinuation.next // advance last to point to the new HtmlContinuationCloseAndIndent
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendable.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA")
4 | open class HtmlContinuationSuspendable {
5 | open suspend fun executeSuspending(model: Any?) {
6 | throw UnsupportedOperationException("Illegal use of executeSuspending!")
7 | }
8 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableAsync.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | import htmlflow.visitor.HtmlVisitor
4 | import kotlinx.coroutines.future.await
5 | import org.xmlet.htmlapifaster.Element
6 | import org.xmlet.htmlapifaster.async.AwaitConsumer
7 | import java.lang.UnsupportedOperationException
8 | import java.util.concurrent.CompletableFuture
9 |
10 |
11 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA")
12 | class HtmlContinuationSuspendableAsync, T>(
13 | currentDepth: Int,
14 | isClosed: Boolean,
15 | private val element: E,
16 | private val consumer: AwaitConsumer,
17 | visitor: HtmlVisitor,
18 | next: HtmlContinuation?
19 | ) : HtmlContinuation(
20 | currentDepth, isClosed, visitor, next
21 | ) {
22 | override suspend fun executeSuspending(model: Any?) {
23 | if (currentDepth >= 0) {
24 | visitor.setIsClosed(isClosed)
25 | visitor.depth = currentDepth
26 | }
27 | val cf = CompletableFuture()
28 | consumer.accept(element, model as T) {
29 | cf.complete(null)
30 | }
31 | cf.await()
32 | next?.executeSuspending(model)
33 | }
34 |
35 | override fun execute(model: Any?) {
36 | throw UnsupportedOperationException("Illegal use of execute in suspendable continuation! Should use executeSuspending.")
37 | }
38 |
39 | override fun copy(v: HtmlVisitor): HtmlContinuation? {
40 | return HtmlContinuationSuspendableAsync (
41 | currentDepth,
42 | isClosed,
43 | copyElement(element, v),
44 | consumer,
45 | v,
46 | next?.copy(v)
47 | ) // call copy recursively
48 | }
49 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSync.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | import htmlflow.visitor.HtmlVisitor
4 |
5 | /**
6 | * @author Miguel Gamboa
7 | */
8 | abstract class HtmlContinuationSuspendableSync(
9 | currentDepth: Int,
10 | isClosed: Boolean,
11 | visitor: HtmlVisitor,
12 | next: HtmlContinuation?
13 | ) : HtmlContinuation(currentDepth, isClosed, visitor, next
14 | ) {
15 | /**
16 | * Executes this continuation and calls the next one if exist.
17 | *
18 | * @param model
19 | */
20 | override fun execute(model: Any?) {
21 | throw UnsupportedOperationException("Illegal use of execute in suspendable continuation! Should use executeSuspending.")
22 | }
23 |
24 | final override suspend fun executeSuspending(model: Any?) {
25 | if (currentDepth >= 0) {
26 | visitor.setIsClosed(isClosed)
27 | visitor.depth = currentDepth
28 | }
29 | emitHtml(model)
30 | next?.executeSuspending(model)
31 | }
32 |
33 | /**
34 | * Hook method to emit HTML.
35 | *
36 | * @param model
37 | */
38 | protected abstract fun emitHtml(model: Any?)
39 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSyncCloseAndIndent.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | import htmlflow.visitor.HtmlVisitor
4 |
5 | /**
6 | * Sets indentation to -1 to inform that visitor should continue with previous indentation.
7 | * The isClosed is useless here.
8 | *
9 | * @author Miguel Gamboa
10 | */
11 | class HtmlContinuationSuspendableSyncCloseAndIndent(
12 | visitor: HtmlVisitor,
13 | next: HtmlContinuation? = null
14 | ) : HtmlContinuationSuspendableSync(-1, false, visitor, next) {
15 |
16 | override fun emitHtml(model: Any?) {
17 | /**
18 | * !!!!! This continuation may follow a dynamic or await block that may create a block or inline element.
19 | * Block elements increment (depth) indentation at the beginning and decrement at the end.
20 | * On the other hand, inline elements keep indentation (depth) unmodified.
21 | * We assume most dynamic or await blocks will create block elements.
22 | * Thus, here we start by decrementing depth.
23 | * However, this could not be true for some cases, and we will get inconsistent indentation !!!!
24 | */
25 | visitor.depth = visitor.depth - 1
26 | visitor.newlineAndIndent()
27 |
28 | }
29 |
30 | override fun copy(visitor: HtmlVisitor): HtmlContinuation? {
31 | return HtmlContinuationSuspendableSyncCloseAndIndent(
32 | visitor,
33 | next?.copy(visitor)
34 | ) // call copy recursively
35 | }
36 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSyncDynamic.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | import htmlflow.visitor.HtmlVisitor
4 | import org.xmlet.htmlapifaster.Element
5 | import java.util.function.BiConsumer
6 |
7 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA")
8 | class HtmlContinuationSuspendableSyncDynamic, T>(
9 | currentDepth: Int,
10 | isClosed: Boolean,
11 | /**
12 | * The element passed to the continuation consumer.
13 | */
14 | private val element: E,
15 | /**
16 | * The continuation that consumes the element and a model.
17 | */
18 | private val consumer: BiConsumer,
19 | visitor: HtmlVisitor,
20 | next: HtmlContinuation?,
21 |
22 | ) : HtmlContinuationSuspendableSync(currentDepth, isClosed, visitor, next) {
23 | override fun emitHtml(model: Any?) {
24 | consumer.accept(element, model as T)
25 | }
26 |
27 | override fun copy(v: HtmlVisitor): HtmlContinuation {
28 | return HtmlContinuationSuspendableSyncDynamic(
29 | currentDepth,
30 | isClosed,
31 | copyElement(element, v),
32 | consumer,
33 | v,
34 | next?.copy(v)
35 | ) // call copy recursively
36 | }
37 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableSyncStatic.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | import htmlflow.visitor.HtmlVisitor
4 |
5 | /**
6 | * HtmlContinuationSuspendable for a static HTML block.
7 | * Sets indentation to -1 to inform that visitor should continue with previous indentation.
8 | * The isClosed is useless because it just writes what it is in its staticHtmlBlock.
9 | * @author Miguel Gamboa
10 | */
11 | class HtmlContinuationSuspendableSyncStatic(
12 | private val staticHtmlBlock: String,
13 | visitor: HtmlVisitor,
14 | next: HtmlContinuation?
15 | ) : HtmlContinuationSuspendableSync(-1, false, visitor, next) { // The isClosed parameter is useless in this case of Static HTML block.
16 |
17 | override fun emitHtml(model: Any?) {
18 | visitor.write(staticHtmlBlock)
19 | }
20 |
21 | override fun copy(v: HtmlVisitor): HtmlContinuation? {
22 | return HtmlContinuationSuspendableSyncStatic(
23 | staticHtmlBlock,
24 | v,
25 | next?.copy(v)
26 | ) // call copy recursively
27 | }
28 | }
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspendableTerminationNode.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | import htmlflow.visitor.HtmlVisitor
4 |
5 | class HtmlContinuationSuspendableTerminationNode : HtmlContinuation(-1, false, null, null) {
6 |
7 | override suspend fun executeSuspending(model: Any?) {
8 | // Nothing to do
9 | }
10 |
11 | override fun execute(model: Any?) {
12 | throw UnsupportedOperationException("Illegal use of suspending terminal node! Only valid in HtmlViewSuspend.")
13 | }
14 |
15 | /**
16 | * Since this Node is used only to signal completion, and we do not use the visitor,
17 | * then we van reuse it.
18 | */
19 | override fun copy(visitor: HtmlVisitor?): HtmlContinuationSuspendableTerminationNode {
20 | return this
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/kotlin/htmlflow/continuations/HtmlContinuationSuspending.kt:
--------------------------------------------------------------------------------
1 | package htmlflow.continuations
2 |
3 | import htmlflow.visitor.HtmlVisitor
4 | import org.xmlet.htmlapifaster.Element
5 | import org.xmlet.htmlapifaster.SuspendConsumer
6 |
7 | /**
8 | * HtmlContinuation for a suspending block (i.e.SuspendConsumer) depending of an asynchronous object model.
9 | * The next continuation will be invoked on completion of the asynchronous object model.
10 | *
11 | * @param the type of the parent HTML element received by the dynamic HTML block.
12 | * @param the type of the template's model.
13 | */
14 | @Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA")
15 | class HtmlContinuationSuspending, T>(
16 | currentDepth: Int,
17 | isClosed: Boolean,
18 | val element: E,
19 | private val consumer: SuspendConsumer,
20 | visitor: HtmlVisitor,
21 | next: HtmlContinuation?
22 | ) : HtmlContinuation(
23 | currentDepth, isClosed, visitor, next
24 | ) {
25 |
26 | override fun execute(model: Any?) {
27 | throw UnsupportedOperationException("Illegal use fo suspending continuation. This should be used with executeSuspend!")
28 | }
29 |
30 | override suspend fun executeSuspending(model: Any?) {
31 | if (currentDepth >= 0) {
32 | this.visitor.setIsClosed(isClosed)
33 | this.visitor.depth = currentDepth
34 | }
35 | /*
36 | * Call this consumer
37 | */
38 | this.consumer.run {
39 | element.accept(model as T?)
40 | }
41 | /*
42 | * Call next consumer
43 | */
44 | next.executeSuspending(model)
45 | }
46 |
47 | override fun copy(v: HtmlVisitor): HtmlContinuation {
48 | return HtmlContinuationSuspending(
49 | currentDepth,
50 | isClosed,
51 | copyElement(element, v),
52 | consumer,
53 | v,
54 | next?.copy(v)
55 | ) // call copy recursively
56 | }
57 | }
--------------------------------------------------------------------------------
/src/main/resources/templates/HtmlView-Header.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/test/java/htmlflow/exceptions/HtmlFlowAppendExceptionTest.java:
--------------------------------------------------------------------------------
1 | package htmlflow.exceptions;
2 |
3 | import org.junit.jupiter.api.DisplayNameGeneration;
4 | import org.junit.jupiter.api.DisplayNameGenerator;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static org.junit.jupiter.api.Assertions.*;
8 |
9 | /**
10 | * @author Pedro Fialho
11 | **/
12 | @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
13 | class HtmlFlowAppendExceptionTest {
14 |
15 | @Test
16 | void given_underlying_exception_should_append_to_error_message() {
17 | Exception ex = new RuntimeException("oops");
18 |
19 | HtmlFlowAppendException htmlFlowAppendException = new HtmlFlowAppendException(ex);
20 |
21 | assertEquals("There has been an exception in writing the Html" +
22 | ", underlying exception is oops", htmlFlowAppendException.getMessage());
23 | }
24 |
25 | @Test
26 | void given_message_should_append_to_error_message() {
27 | HtmlFlowAppendException htmlFlowAppendException = new HtmlFlowAppendException("oops");
28 |
29 | assertEquals("There has been an exception in writing the Html" +
30 | ", underlying exception is oops", htmlFlowAppendException.getMessage());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/htmlflow/test/TestAsyncViewInConcurInMultpleThreads.java:
--------------------------------------------------------------------------------
1 | package htmlflow.test;
2 |
3 | import htmlflow.HtmlFlow;
4 | import htmlflow.HtmlPage;
5 | import htmlflow.HtmlView;
6 | import htmlflow.HtmlViewAsync;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import java.util.function.Supplier;
10 |
11 | import static org.junit.Assert.assertEquals;
12 |
13 | class TestAsyncViewInConcurInMultpleThreads {
14 |
15 | @Test
16 | void testMultipleThreadsInViewAsync() throws InterruptedException {
17 | HtmlViewAsync