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 private boolean isComplete;
58 private boolean isTitle;
59
60
61
62 @Deprecated
63 public IndexingSink(IndexEntry rootEntry) {
64 this(rootEntry, new SinkAdapter());
65 }
66
67 public IndexingSink(Sink delegate) {
68 this(new IndexEntry("index"), delegate);
69 }
70
71
72
73
74 private IndexingSink(IndexEntry rootEntry, Sink delegate) {
75 super(delegate);
76 this.rootEntry = rootEntry;
77 stack = new Stack<>();
78 stack.push(rootEntry);
79 usedIds = new HashMap<>();
80 usedIds.put(rootEntry.getId(), new AtomicInteger());
81 this.type = Type.UNKNOWN;
82 }
83
84
85
86
87
88
89
90 public IndexEntry getRootEntry() {
91 if (!isComplete) {
92 throw new IllegalStateException(
93 "The sink has not been closed yet, i.e. the index tree is not complete yet");
94 }
95 return rootEntry;
96 }
97
98
99
100
101
102
103 public String getTitle() {
104 return rootEntry.getTitle();
105 }
106
107
108
109
110
111 @Override
112 public void title(SinkEventAttributes attributes) {
113 isTitle = true;
114 super.title(attributes);
115 }
116
117 @Override
118 public void title_() {
119 isTitle = false;
120 super.title_();
121 }
122
123 @Override
124 public void section(int level, SinkEventAttributes attributes) {
125 super.section(level, attributes);
126 this.type = IndexEntry.Type.fromSectionLevel(level);
127 pushNewEntry(type);
128 }
129
130 @Override
131 public void section_(int level) {
132 pop();
133 super.section_(level);
134 }
135
136 @Override
137 public void sectionTitle_(int level) {
138 indexEntryComplete();
139 super.sectionTitle_(level);
140 }
141
142 @Override
143 public void text(String text, SinkEventAttributes attributes) {
144 if (isTitle) {
145 rootEntry.setTitle(text);
146 return;
147 }
148 switch (this.type) {
149 case SECTION_1:
150 case SECTION_2:
151 case SECTION_3:
152 case SECTION_4:
153 case SECTION_5:
154
155
156
157
158
159 IndexEntry entry = stack.lastElement();
160
161 String title = entry.getTitle() + text;
162 title = title.replaceAll("[\\r\\n]+", "");
163 entry.setTitle(title);
164
165 setEntryId(entry, title);
166 break;
167
168 default:
169 break;
170 }
171 super.text(text, attributes);
172 }
173
174 @Override
175 public void anchor(String name, SinkEventAttributes attributes) {
176 parseAnchor(name);
177 super.anchor(name, attributes);
178 }
179
180 private boolean parseAnchor(String name) {
181 switch (type) {
182 case SECTION_1:
183 case SECTION_2:
184 case SECTION_3:
185 case SECTION_4:
186 case SECTION_5:
187 IndexEntry entry = stack.lastElement();
188 entry.setAnchor(true);
189 setEntryId(entry, name);
190 break;
191 default:
192 return false;
193 }
194 return true;
195 }
196
197 private void setEntryId(IndexEntry entry, String id) {
198 if (entry.getId() != null) {
199 usedIds.remove(entry.getId());
200 }
201 entry.setId(getUniqueId(DoxiaUtils.encodeId(id)));
202 }
203
204
205
206
207
208
209
210 String getUniqueId(String id) {
211 final String uniqueId;
212
213 if (usedIds.containsKey(id)) {
214 uniqueId = id + "_" + usedIds.get(id).incrementAndGet();
215 } else {
216 usedIds.put(id, new AtomicInteger());
217 uniqueId = id;
218 }
219 return uniqueId;
220 }
221
222 void indexEntryComplete() {
223 this.type = Type.UNKNOWN;
224
225 BufferingSink bufferingSink = BufferingSinkProxyFactory.castAsBufferingSink(getWrappedSink());
226 setWrappedSink(bufferingSink.getBufferedSink());
227
228 onIndexEntry(stack.peek());
229
230
231 bufferingSink.flush();
232 }
233
234
235
236
237
238
239 protected void onIndexEntry(IndexEntry entry) {}
240
241
242
243
244 private void pushNewEntry(Type type) {
245 IndexEntry entry = new IndexEntry(peek(), "", type);
246 entry.setTitle("");
247 stack.push(entry);
248
249 setWrappedSink(new BufferingSinkProxyFactory().createWrapper(getWrappedSink()));
250 }
251
252
253
254
255
256
257 public void push(IndexEntry entry) {
258 stack.push(entry);
259 }
260
261
262
263
264 public void pop() {
265 stack.pop();
266 }
267
268
269
270
271
272
273 public IndexEntry peek() {
274 return stack.peek();
275 }
276
277 @Override
278 public void close() {
279 super.close();
280 isComplete = true;
281 }
282 }