Skip to content

Method: DefaultLayout(Layout.Builder)

1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * NorthernWind - lightweight CMS
5: * http://tidalwave.it/projects/northernwind
6: *
7: * Copyright (C) 2011 - 2025 Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *************************************************************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12: * You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * 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
17: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18: *
19: * *************************************************************************************************************************************************************
20: *
21: * git clone https://bitbucket.org/tidalwave/northernwind-src
22: * git clone https://github.com/tidalwave-it/northernwind-src
23: *
24: * *************************************************************************************************************************************************************
25: */
26: package it.tidalwave.northernwind.frontend.impl.ui;
27:
28: import javax.annotation.Nonnull;
29: import javax.inject.Inject;
30: import java.util.ArrayDeque;
31: import java.util.ArrayList;
32: import java.util.Deque;
33: import java.util.HashMap;
34: import java.util.List;
35: import java.util.Map;
36: import java.util.Optional;
37: import org.springframework.beans.factory.annotation.Configurable;
38: import it.tidalwave.util.As;
39: import it.tidalwave.util.Id;
40: import it.tidalwave.util.NotFoundException;
41: import it.tidalwave.northernwind.core.model.HttpStatusException;
42: import it.tidalwave.northernwind.core.model.SiteNode;
43: import it.tidalwave.northernwind.frontend.ui.Layout;
44: import it.tidalwave.northernwind.frontend.ui.LayoutFinder;
45: import it.tidalwave.northernwind.frontend.ui.ViewFactory;
46: import it.tidalwave.northernwind.frontend.ui.ViewFactory.ViewAndController;
47: import lombok.Getter;
48: import lombok.experimental.Delegate;
49:
50: /***************************************************************************************************************************************************************
51: *
52: * @author Fabrizio Giudici
53: *
54: **************************************************************************************************************************************************************/
55: @Configurable @Getter
56: public class DefaultLayout implements Layout, Cloneable
57: {
58: @Inject
59: private ViewFactory viewFactory;
60:
61: @Nonnull
62: private final Id id;
63:
64: @Nonnull
65: private /* final FIXME */ String typeUri;
66:
67: private final List<Layout> children = new ArrayList<>();
68:
69: private final Map<Id, Layout> childrenMapById = new HashMap<>();
70:
71: @Delegate
72: private final As asSupport = As.forObject(this);
73:
74: // FIXME: make it Immutable
75:
76: /***********************************************************************************************************************************************************
77: *
78: **********************************************************************************************************************************************************/
79: static class CloneVisitor implements Visitor<Layout, DefaultLayout>
80: {
81: private DefaultLayout rootLayout;
82:
83: private final Deque<DefaultLayout> layoutStack = new ArrayDeque<>();
84:
85: @Override
86: public void preVisit (@Nonnull final Layout layout)
87: {
88: final var clone = new DefaultLayout(((DefaultLayout)layout).id,
89: ((DefaultLayout)layout).typeUri);
90:
91: if (rootLayout == null)
92: {
93: rootLayout = clone;
94: }
95: else
96: {
97: layoutStack.peek().add(clone);
98: }
99:
100: layoutStack.push(clone);
101: }
102:
103: @Override
104: public void postVisit (@Nonnull final Layout layout)
105: {
106: layoutStack.pop();
107: }
108:
109: @Override @Nonnull
110: public Optional<DefaultLayout> getValue()
111: {
112: return Optional.of(rootLayout);
113: }
114: }
115:
116: /***********************************************************************************************************************************************************
117: *
118: **********************************************************************************************************************************************************/
119: public DefaultLayout()
120: {
121: this.id = new Id("");
122: this.typeUri = "";
123: }
124:
125: /** Clone - FIXME: public only for InfoglueImporter */
126: /***********************************************************************************************************************************************************
127: *
128: **********************************************************************************************************************************************************/
129: public DefaultLayout (@Nonnull final Id id, @Nonnull final String typeUri)
130: {
131: this.id = id;
132: this.typeUri = typeUri;
133: }
134:
135: /***********************************************************************************************************************************************************
136: *
137: **********************************************************************************************************************************************************/
138: public DefaultLayout (@Nonnull final Builder builder)
139:• {
140: this.id = builder.getId();
141: this.typeUri = builder.getType();
142:• }
143:
144: /***********************************************************************************************************************************************************
145: * {@inheritDoc}
146: **********************************************************************************************************************************************************/
147: @Override @Nonnull
148: public DefaultLayout clone()
149: {
150: return accept(new CloneVisitor()).orElseThrow(RuntimeException::new);
151: }
152:
153: /***********************************************************************************************************************************************************
154: * {@inheritDoc}
155: **********************************************************************************************************************************************************/
156: @Override @Nonnull
157: public Layout withOverride (@Nonnull final Layout override)
158: {
159: final var result = clone();
160: result.applyOverride(((DefaultLayout)override).clone());
161: return result;
162: }
163:
164: /***********************************************************************************************************************************************************
165: * {@inheritDoc}
166: **********************************************************************************************************************************************************/
167: @Override @Nonnull
168: public Layout withChild (@Nonnull final Layout layout)
169: {
170: final var clone = clone();
171: clone.children.add(layout);
172: clone.childrenMapById.put(layout.getId(), layout);
173:
174: return clone;
175: }
176:
177: /***********************************************************************************************************************************************************
178: * {@inheritDoc}
179: **********************************************************************************************************************************************************/
180: @Override @Nonnull
181: public LayoutFinder findChildren()
182: {
183: return new DefaultLayoutFinder(children, childrenMapById);
184: }
185:
186: /***********************************************************************************************************************************************************
187: * {@inheritDoc}
188: **********************************************************************************************************************************************************/
189: public void add (@Nonnull final Layout layout) // FIXME: drop this - used only by the CloneVisitor and Infoglue converter
190: {
191: children.add(layout); // FIXME: clone
192: childrenMapById.put(layout.getId(), layout);// FIXME: clone
193: }
194:
195: /***********************************************************************************************************************************************************
196: * {@inheritDoc}
197: **********************************************************************************************************************************************************/
198: @Override @Nonnull
199: public ViewAndController createViewAndController (@Nonnull final SiteNode siteNode)
200: throws NotFoundException, HttpStatusException
201: {
202: return viewFactory.createViewAndController(typeUri, id, siteNode);
203: }
204:
205: /***********************************************************************************************************************************************************
206: * {@inheritDoc}
207: **********************************************************************************************************************************************************/
208: @Override @Nonnull // TODO: push up to CompositeSupport
209: public <T> Optional<T> accept (@Nonnull final Visitor<? super Layout, T> visitor)
210: {
211: visitor.preVisit(this);
212: visitor.visit(this);
213:
214: for (final var child : children)
215: {
216: child.accept(visitor);
217: }
218:
219: visitor.postVisit(this);
220: return visitor.getValue();
221: }
222:
223: /***********************************************************************************************************************************************************
224: * {@inheritDoc}
225: **********************************************************************************************************************************************************/
226: @Override @Nonnull
227: public String toString()
228: {
229: return String.format("DefaultLayout(id=%s, typeUri=%s, children.count=%d)", id, typeUri, children.size());
230: }
231:
232: /***********************************************************************************************************************************************************
233: *
234: **********************************************************************************************************************************************************/
235: // Here everything is already cloned
236: private void applyOverride (@Nonnull final Layout override)
237: {
238: final var sameType = this.getTypeUri().equals(override.getTypeUri());
239: this.typeUri = override.getTypeUri(); // FIXME: don't like this approach, as it requires typeUri non final
240:
241: // Complex rule, but it's to keep compatibility with Infoglue.
242: if (sameType)
243: {
244: for (final var overridingChild : override.findChildren().results())
245: {
246: final var overriddenChild = childrenMapById.get(overridingChild.getId());
247:
248: if (overriddenChild == null)
249: {
250: add(overridingChild);
251: }
252: else
253: {
254: childrenMapById.put(overridingChild.getId(), overridingChild);
255: final var i = children.indexOf(overriddenChild);
256:
257: if (i < 0)
258: {
259: throw new IllegalArgumentException();
260: }
261:
262: children.set(i, overridingChild);
263: // ((DefaultLayout)overriddenChild).applyOverride(overridingChild);
264: }
265: }
266: }
267: else
268: {
269: this.children.clear();
270: this.childrenMapById.clear();
271:
272: for (final var overridingChild : override.findChildren().results())
273: {
274: add(overridingChild);
275: }
276: }
277: }
278: }