1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.doxia.index;
20
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Stack;
24 import java.util.concurrent.atomic.AtomicInteger;
25
26 import org.apache.maven.doxia.index.IndexEntry.Type;
27 import org.apache.maven.doxia.sink.Sink;
28 import org.apache.maven.doxia.sink.SinkEventAttributes;
29 import org.apache.maven.doxia.sink.impl.BufferingSinkProxyFactory;
30 import org.apache.maven.doxia.sink.impl.BufferingSinkProxyFactory.BufferingSink;
31 import org.apache.maven.doxia.sink.impl.SinkAdapter;
32 import org.apache.maven.doxia.util.DoxiaUtils;
33
34
35
36
37
38
39
40
41 public class IndexingSink extends org.apache.maven.doxia.sink.impl.SinkWrapper {
42
43
44 private Type type;
45
46
47 private final Stack<IndexEntry> stack;
48
49
50
51
52
53 private final Map<String, AtomicInteger> usedIds;
54
55 private final IndexEntry rootEntry;
56
57
58 private boolean isComplete;
59
60 private boolean isTitle;
61
62
63 private boolean hasOpenEntry;
64
65
66
67
68 @Deprecated
69 public IndexingSink(IndexEntry rootEntry) {
70 this(rootEntry, new SinkAdapter());
71 }
72
73 public IndexingSink(Sink delegate) {
74 this(new IndexEntry("index"), delegate);
75 }
76
77
78
79
80 private IndexingSink(IndexEntry rootEntry, Sink delegate) {
81 super(delegate);
82 this.rootEntry = rootEntry;
83 stack = new Stack<>();
84 stack.push(rootEntry);
85 usedIds = new HashMap<>();
86 usedIds.put(rootEntry.getId(), new AtomicInteger());
87 this.type = Type.UNKNOWN;
88 }
89
90
91
92
93
94
95
96 public IndexEntry getRootEntry() {
97 if (!isComplete) {
98 throw new IllegalStateException(
99 "The sink has not been closed yet, i.e. the index tree is not complete yet");
100 }
101 return rootEntry;
102 }
103
104
105
106
107
108
109 public String getTitle() {
110 return rootEntry.getTitle();
111 }
112
113
114
115
116
117 @Override
118 public void title(SinkEventAttributes attributes) {
119 isTitle = true;
120 super.title(attributes);
121 }
122
123 @Override
124 public void title_() {
125 isTitle = false;
126 super.title_();
127 }
128
129 @Override
130 public void section(int level, SinkEventAttributes attributes) {
131 super.section(level, attributes);
132 indexEntryComplete();
133 this.type = IndexEntry.Type.fromSectionLevel(level);
134 pushNewEntry(type);
135 }
136
137 @Override
138 public void section_(int level) {
139 indexEntryComplete();
140 pop();
141 super.section_(level);
142 }
143
144 @Override
145 public void sectionTitle_(int level) {
146 indexEntryComplete();
147 super.sectionTitle_(level);
148 }
149
150 @Override
151 public void text(String text, SinkEventAttributes attributes) {
152 if (isTitle) {
153 rootEntry.setTitle(text);
154 } else {
155 switch (this.type) {
156 case SECTION_1:
157 case SECTION_2:
158 case SECTION_3:
159 case SECTION_4:
160 case SECTION_5:
161 case SECTION_6:
162
163
164
165
166
167 IndexEntry entry = stack.lastElement();
168
169 String title = entry.getTitle();
170 if (title != null) {
171 title += text;
172 } else {
173 title = text;
174 }
175 title = title.replaceAll("[\\r\\n]+", "");
176 entry.setTitle(title);
177
178 setEntryId(entry, title);
179 break;
180
181 default:
182 break;
183 }
184 }
185 super.text(text, attributes);
186 }
187
188 @Override
189 public void anchor(String name, SinkEventAttributes attributes) {
190 parseAnchor(name);
191 super.anchor(name, attributes);
192 }
193
194 private boolean parseAnchor(String name) {
195 switch (type) {
196 case SECTION_1:
197 case SECTION_2:
198 case SECTION_3:
199 case SECTION_4:
200 case SECTION_5:
201 IndexEntry entry = stack.lastElement();
202 entry.setAnchor(true);
203 setEntryId(entry, name);
204 break;
205 default:
206 return false;
207 }
208 return true;
209 }
210
211 private void setEntryId(IndexEntry entry, String id) {
212 if (entry.getId() != null) {
213 usedIds.remove(entry.getId());
214 }
215 entry.setId(getUniqueId(DoxiaUtils.encodeId(id)));
216 }
217
218
219
220
221
222
223
224 String getUniqueId(String id) {
225 final String uniqueId;
226
227 if (usedIds.containsKey(id)) {
228 uniqueId = id + "_" + usedIds.get(id).incrementAndGet();
229 } else {
230 usedIds.put(id, new AtomicInteger());
231 uniqueId = id;
232 }
233 return uniqueId;
234 }
235
236 void indexEntryComplete() {
237 if (!hasOpenEntry) {
238 return;
239 }
240 this.type = Type.UNKNOWN;
241
242 BufferingSink bufferingSink = BufferingSinkProxyFactory.castAsBufferingSink(getWrappedSink());
243 setWrappedSink(bufferingSink.getBufferedSink());
244
245 onIndexEntry(stack.peek());
246
247
248 bufferingSink.flush();
249 hasOpenEntry = false;
250 }
251
252
253
254
255
256
257 protected void onIndexEntry(IndexEntry entry) {}
258
259
260
261
262 private void pushNewEntry(Type type) {
263 IndexEntry entry = new IndexEntry(peek(), null, type);
264 stack.push(entry);
265
266 setWrappedSink(new BufferingSinkProxyFactory().createWrapper(getWrappedSink()));
267 hasOpenEntry = true;
268 }
269
270
271
272
273
274
275 public void push(IndexEntry entry) {
276 stack.push(entry);
277 }
278
279
280
281
282 public void pop() {
283 stack.pop();
284 }
285
286
287
288
289
290
291 public IndexEntry peek() {
292 return stack.peek();
293 }
294
295 @Override
296 public void close() {
297 super.close();
298 isComplete = true;
299 }
300 }