1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.devacfr.maven.skins.reflow;
17
18 import static java.util.Objects.requireNonNull;
19
20 import com.google.common.base.Strings;
21 import java.text.ParseException;
22 import java.text.SimpleDateFormat;
23 import java.util.Date;
24 import java.util.List;
25 import java.util.Optional;
26 import javax.annotation.Nonnull;
27 import javax.annotation.Nullable;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.maven.doxia.site.PublishDate;
30 import org.apache.maven.doxia.site.SiteModel;
31 import org.apache.maven.project.MavenProject;
32 import org.apache.velocity.tools.ToolContext;
33 import org.apache.velocity.tools.config.DefaultKey;
34 import org.apache.velocity.tools.generic.DateTool;
35 import org.apache.velocity.tools.generic.RenderTool;
36 import org.apache.velocity.tools.generic.ResourceTool;
37 import org.apache.velocity.tools.generic.SafeConfig;
38 import org.apache.velocity.tools.generic.ValueParser;
39 import org.codehaus.plexus.util.PathTool;
40 import org.codehaus.plexus.util.xml.Xpp3Dom;
41 import org.devacfr.maven.skins.reflow.context.Context;
42 import org.devacfr.maven.skins.reflow.context.PositionType;
43 import org.devacfr.maven.skins.reflow.snippet.Component;
44 import org.devacfr.maven.skins.reflow.snippet.SnippetComponent;
45 import org.devacfr.maven.skins.reflow.snippet.SnippetContext;
46 import org.devacfr.maven.skins.reflow.snippet.SnippetParser;
47 import org.jsoup.nodes.Document;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 @DefaultKey("config")
96 public class SkinConfigTool extends SafeConfig implements ISkinConfig {
97
98
99 private static final Logger LOGGER = LoggerFactory.getLogger(SkinConfigTool.class);
100
101
102 private static final SimpleDateFormat ISO_8601BASIC_DATE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
103
104 private static final String PROJECT_BUILD_OUTPUTTIMESTAMP = "project.build.outputTimestamp";
105
106
107 public static final String DEFAULT_KEY = "config";
108
109
110 public static final String SKIN_KEY = "reflowSkin";
111
112
113 private String key = DEFAULT_KEY;
114
115
116 private String skinKey = SKIN_KEY;
117
118
119 private Xpp3Dom globalProperties = new Xpp3Dom("");
120
121
122 private Xpp3Dom pageProperties = new Xpp3Dom("");
123
124
125 private String namespace = "";
126
127
128 private String projectId = null;
129
130
131 private String fileId = null;
132
133
134 private Context<?> context = null;
135
136
137 private MavenProject project = null;
138
139
140 private SiteModel siteModel;
141
142
143 private ToolContext velocityContext;
144
145
146
147
148
149
150 @Override
151 protected void configure(final ValueParser values) {
152 final String altkey = values.getString("key");
153 if (altkey != null) {
154 setKey(altkey);
155 }
156
157
158 final String altSkinKey = values.getString("skinKey");
159 if (altSkinKey != null) {
160 this.skinKey = altSkinKey;
161 }
162
163
164 final Object vc = values.get("velocityContext");
165
166 if (!(vc instanceof ToolContext)) {
167 return;
168 }
169
170 this.velocityContext = (ToolContext) vc;
171
172 final Object projectObj = velocityContext.get("project");
173 if (projectObj instanceof MavenProject) {
174 this.project = (MavenProject) projectObj;
175 final String artifactId = project.getArtifactId();
176
177 projectId = HtmlTool.slug(artifactId);
178 }
179
180
181 final String currentFileObj = getCurrentFileName();
182 fileId = slugFilename(currentFileObj);
183
184 final Object siteModelObj = velocityContext.get("site");
185
186 if (!(siteModelObj instanceof SiteModel)) {
187 return;
188 }
189
190 this.siteModel = (SiteModel) siteModelObj;
191 final Object customObj = siteModel.getCustom();
192
193 if (!(customObj instanceof Xpp3Dom)) {
194 return;
195 }
196
197
198
199 final Xpp3Dom customNode = (Xpp3Dom) customObj;
200 Xpp3Dom skinNode = customNode.getChild(skinKey);
201 final String namespaceKey = ":" + skinKey;
202
203 if (skinNode == null) {
204
205 for (final Xpp3Dom child : customNode.getChildren()) {
206 if (child.getName().endsWith(namespaceKey)) {
207 skinNode = child;
208 break;
209 }
210 }
211 }
212
213 if (skinNode != null) {
214 globalProperties = skinNode;
215
216 if (skinNode.getName().endsWith(namespaceKey)) {
217
218 namespace = Strings
219 .emptyToNull(skinNode.getName().substring(0, skinNode.getName().length() - namespaceKey.length() + 1));
220 }
221
222
223
224 final Xpp3Dom pagesNode = Xpp3Utils.getFirstChild(skinNode, "pages", namespace);
225 if (pagesNode != null) {
226
227
228 Xpp3Dom page = Xpp3Utils.getFirstChild(pagesNode, fileId, namespace);
229
230
231
232
233 if (page != null && projectId != null) {
234 final String pageProject = page.getAttribute("project");
235 if (pageProject != null && !projectId.equals(pageProject)) {
236
237 page = null;
238 }
239 }
240
241 if (page != null) {
242 pageProperties = page;
243 }
244 }
245
246
247
248 this.velocityContext.put("localResources", is("localResources"));
249
250
251 this.velocityContext.put("resourcePath", getResourcePath());
252
253 this.context = Context.buildContext(this);
254 }
255 if (LOGGER.isDebugEnabled()) {
256 LOGGER.debug("Current Filename: {}", currentFileObj);
257 LOGGER.debug("title: {}", velocityContext.get("title"));
258 LOGGER.debug("shortTitle: {}", velocityContext.get("shortTitle"));
259 LOGGER.debug("Project id: {}", projectId);
260 LOGGER.debug("File id: {}", fileId);
261 LOGGER.debug("Context: {}", this.context);
262 LOGGER.debug("Namespace: {}", this.namespace);
263 LOGGER.debug("---------------------------------------------------");
264 }
265 }
266
267
268
269
270 @Nonnull
271 @Override
272 public String renderSnippets(final String bodyContent) throws Exception {
273 final SnippetContext snippetContext = new SnippetParser().parse(this, bodyContent);
274 return snippetContext.html();
275 }
276
277
278
279
280 @Nonnull
281 @Override
282 public String renderSnippet(final String snippet) throws Exception {
283 final SnippetParser parser = new SnippetParser();
284 final Document doc = getHtmlTool().parse(snippet);
285 final SnippetContext context = parser.getSnippetContext();
286 final Component<?> component = context.create(doc.body().firstElementChild(), null);
287 context.setConfig(this);
288 if (component instanceof SnippetComponent<?>) {
289 ((SnippetComponent<?>) component).render(context);
290 }
291 return doc.body().html();
292 }
293
294
295
296
297
298
299
300
301 protected void setKey(final String key) {
302 this.key = requireNonNull(key, "SkinConfigTool key cannot be null");
303 }
304
305
306
307
308
309 public String getKey() {
310 return this.key;
311 }
312
313
314
315
316 @Override
317 @Nullable public <T> T getContextValue(@Nonnull final String key, @Nonnull final Class<T> type) {
318 requireNonNull(type);
319 if (String.class.isAssignableFrom(type)) {
320 return this.eval("$" + key, type);
321 } else {
322 throw new UnsupportedOperationException();
323 }
324 }
325
326
327
328
329 @Override
330 public void setContextValue(@Nonnull final String key, @Nullable final Object value) {
331 requireNonNull(key);
332 if (value instanceof String) {
333 this.eval("#set( $" + key + "= \"" + value.toString() + "\")", Void.class);
334 } else {
335 throw new UnsupportedOperationException();
336 }
337 }
338
339
340
341
342 @Override
343 @Nullable @SuppressWarnings("unchecked")
344 public <T> T getToolbox(@Nonnull final String toolName, @Nonnull final Class<T> toolType) {
345 requireNonNull(toolType);
346 return (T) this.velocityContext.getToolbox().get(requireNonNull(toolName));
347 }
348
349
350
351
352
353
354
355 @Override
356 @Nonnull
357 public HtmlTool getHtmlTool() {
358 return requireNonNull(getToolbox("htmlTool", HtmlTool.class), "htmlTool is required");
359 }
360
361
362
363
364 @Override
365 @Nullable public Xpp3Dom get(@Nonnull final String property) {
366 requireNonNull(property);
367
368 Xpp3Dom propNode = Xpp3Utils.getFirstChild(pageProperties, property, namespace);
369 if (propNode == null) {
370
371 propNode = Xpp3Utils.getFirstChild(globalProperties, property, namespace);
372 }
373
374 return propNode;
375 }
376
377
378
379
380
381
382
383
384
385
386 @Nullable public String value(@Nonnull final String property) {
387 requireNonNull(property);
388 final Xpp3Dom propNode = get(property);
389
390 if (propNode == null) {
391
392 return null;
393 }
394
395 return propNode.getValue();
396 }
397
398
399
400
401 @Override
402 @Nullable public <T> T getPropertyValue(@Nonnull final String property,
403 @Nonnull final Class<T> targetType,
404 @Nullable final T defaultValue) {
405 return getPropertyValue(property, targetType).orElse(defaultValue);
406 }
407
408
409
410
411 @Override
412 @SuppressWarnings("unchecked")
413 @Nonnull
414 public <T> Optional<T> getPropertyValue(@Nonnull final String property, @Nonnull final Class<T> targetType) {
415 requireNonNull(property, "property is required");
416 requireNonNull(targetType, "targetType is required");
417 final String value = value(property);
418 if (value == null) {
419 return Optional.empty();
420 }
421 Object returnedValue = value;
422 if (targetType.isAssignableFrom(Boolean.class)) {
423 returnedValue = Boolean.valueOf(value);
424 } else if (targetType.isAssignableFrom(Integer.class)) {
425 returnedValue = Integer.valueOf(value);
426 } else if (targetType.isAssignableFrom(Long.class)) {
427 returnedValue = Long.valueOf(value);
428 }
429 return Optional.ofNullable((T) returnedValue);
430 }
431
432
433
434
435
436
437
438
439
440
441 public List<String> getChildren(final Xpp3Dom parentNode) {
442 return Xpp3Utils.getChildren(parentNode);
443 }
444
445
446
447
448 @Override
449 @Nullable public <T> T getAttributeValue(@Nonnull final String property,
450 @Nonnull final String attribute,
451 @Nonnull final Class<T> targetType,
452 @Nullable final T defaultValue) {
453 return getAttributeValue(property, attribute, targetType).orElse(defaultValue);
454 }
455
456
457
458
459 @Override
460 @SuppressWarnings("unchecked")
461 @Nonnull
462 public <T> Optional<T> getAttributeValue(@Nonnull final String property,
463 @Nonnull final String attribute,
464 @Nonnull final Class<T> targetType) {
465 requireNonNull(property, "property is required");
466 requireNonNull(attribute, "attribute is required");
467 requireNonNull(targetType, "targetType is required");
468
469 Xpp3Dom element = get(property);
470 if (element == null) {
471 return Optional.empty();
472 }
473 String value = element.getAttribute(attribute);
474 if (value == null) {
475 return Optional.empty();
476 }
477
478 if ("inherit".equals(value) || Strings.isNullOrEmpty(value)) {
479 element = Xpp3Utils.getFirstChild(globalProperties, property, namespace);
480 if (LOGGER.isDebugEnabled()) {
481 LOGGER.debug("Inherit value property '{}': {}", property, element);
482 }
483 }
484 if (element == null) {
485 return Optional.empty();
486 }
487 value = element.getAttribute(attribute);
488 if (value == null) {
489 return Optional.empty();
490 }
491
492 Object returnedValue = value;
493 if (targetType.isAssignableFrom(Boolean.class)) {
494 returnedValue = Boolean.valueOf(value);
495 } else if (targetType.isAssignableFrom(Integer.class)) {
496 returnedValue = Integer.valueOf(value);
497 } else if (targetType.isAssignableFrom(Long.class)) {
498 returnedValue = Long.valueOf(value);
499 }
500 return Optional.ofNullable((T) returnedValue);
501 }
502
503
504
505
506 @Override
507 @Nullable public <T> T getAttributeValue(@Nonnull final Xpp3Dom element,
508 @Nonnull final String attribute,
509 @Nonnull final Class<T> targetType,
510 @Nullable final T defaultValue) {
511 return getAttributeValue(element, attribute, targetType).orElse(defaultValue);
512 }
513
514
515
516
517 @Override
518 @SuppressWarnings("unchecked")
519 @Nonnull
520 public <T> Optional<T> getAttributeValue(@Nonnull final Xpp3Dom element,
521 @Nonnull final String attribute,
522 @Nonnull final Class<T> targetType) {
523 final String value = element.getAttribute(attribute);
524 if (value == null) {
525 return Optional.empty();
526 }
527 Object returnedValue = value;
528 if (targetType.isAssignableFrom(Boolean.class)) {
529 returnedValue = Boolean.valueOf(value);
530 } else if (targetType.isAssignableFrom(Integer.class)) {
531 returnedValue = Integer.valueOf(value);
532 } else if (targetType.isAssignableFrom(Long.class)) {
533 returnedValue = Long.valueOf(value);
534 }
535 return Optional.ofNullable((T) returnedValue);
536 }
537
538
539
540
541
542
543
544
545
546
547 public boolean is(final String property) {
548 return "true".equals(value(property));
549 }
550
551
552
553
554 @Override
555 public boolean not(final String property) {
556 return "false".equals(value(property));
557 }
558
559
560
561
562
563
564
565
566
567
568
569
570 public boolean isValue(final String property, final String value) {
571 return value != null && value.equals(value(property));
572 }
573
574
575
576
577 @Override
578 @Nullable public String getProjectId() {
579 return projectId;
580 }
581
582
583
584
585 @Override
586 @Nullable public String getFileId() {
587 return fileId;
588 }
589
590
591
592
593 @SuppressWarnings("unchecked")
594 @Override
595 @Nonnull
596 public <T extends Context<Context<?>>> T getContext() {
597 return (T) context;
598 }
599
600
601
602
603 @Override
604 public org.apache.velocity.context.Context getVelocityContext() {
605 return velocityContext;
606 }
607
608
609
610
611 @Override
612 @Nonnull
613 public MavenProject getProject() {
614 return project;
615 }
616
617
618
619
620 @Override
621 @Nonnull
622 public SiteModel getSiteModel() {
623 return siteModel;
624 }
625
626
627
628
629 @Override
630 @Nonnull
631 public Xpp3Dom getPageProperties() {
632 return pageProperties;
633 }
634
635
636
637
638 @Override
639 @Nonnull
640 public Xpp3Dom getGlobalProperties() {
641 return globalProperties;
642 }
643
644
645
646
647 @SuppressWarnings("null")
648 @Override
649 @Nonnull
650 public String getShortTitle() {
651 String shortTitle = getPropertyValue("shortTitle", String.class).orElse((String) velocityContext.get("shortTitle"));
652
653 if (shortTitle == null || shortTitle.isEmpty() || "generate".equalsIgnoreCase(shortTitle)
654 || "true".equalsIgnoreCase(shortTitle)) {
655
656
657
658
659 final HtmlTool htmlTool = getHtmlTool();
660 final String body = getBodyContent();
661 List<String> hTexts = htmlTool.text(body, "h1");
662 if (hTexts.isEmpty()) {
663 hTexts = htmlTool.text(body, "h2");
664 }
665 if (!hTexts.isEmpty()) {
666 shortTitle = hTexts.get(0);
667 }
668 }
669 return shortTitle;
670 }
671
672
673
674
675 @Override
676 @Nonnull
677 public String getTitle() {
678 final String shortTitle = this.getShortTitle();
679 String title = getPropertyValue("title", String.class).orElse((String) velocityContext.get("title"));;
680
681
682 String titleTemplate = getPropertyValue("titleTemplate", String.class).orElse("%1$s - %2$s");
683 if (shortTitle != null) {
684 final SiteModel model = this.getSiteModel();
685
686
687 String titleProjectName = title;
688 if (titleProjectName == null || titleProjectName.isEmpty()) {
689 titleProjectName = null;
690 if (model.getName() != null) {
691 titleProjectName = model.getName();
692 } else if (getProject() != null && getProject().getName() != null) {
693 titleProjectName = getProject().getName();
694 }
695 }
696
697 if (titleProjectName == null) {
698 return shortTitle;
699 }
700
701 try {
702
703 title = String.format(titleTemplate, titleProjectName, shortTitle);
704 } catch (final Exception e) {
705 LOGGER.warn(
706 "Cannot format title with template '{}' and tiltle '{}' and short title '{}', falling back to default format.",
707 titleTemplate,
708 titleProjectName,
709 title,
710 e);
711 }
712 }
713 return title;
714 }
715
716
717
718
719 @SuppressWarnings("null")
720 @Override
721 @Nonnull
722 public String getPublishDate() {
723 final SiteModel model = this.getSiteModel();
724 final PublishDate publishDate = model.getPublishDate();
725 String publishDateValue = null;
726 String format = "yyyy-MM-dd";
727 if (!"none".equals(publishDate.getPosition())) {
728 if (publishDate.getFormat() != null && !publishDate.getFormat().isEmpty()) {
729 format = publishDate.getFormat();
730 }
731 Date outputTimestamp = null;
732 try {
733 outputTimestamp = getBuildOutputTimestamp();
734 } catch (final ParseException e) {
735 LOGGER.warn("Cannot parse the build output timestamp!", e);
736 return null;
737 }
738
739 final DateTool dateTool = getToolbox("date", DateTool.class);
740 if (dateTool != null) {
741 if (outputTimestamp != null) {
742 publishDateValue = dateTool.format(format, outputTimestamp);
743 } else {
744 publishDateValue = dateTool.get(format);
745 }
746 }
747 }
748 return publishDateValue;
749 }
750
751
752
753
754 @SuppressWarnings("null")
755 @Override
756 @Nonnull
757 public String getPublishDateMessage() {
758 final String publishDate = getPublishDate();
759 if (Strings.isNullOrEmpty(publishDate)) {
760 return null;
761 }
762 final ResourceTool text = this.getToolbox("text", ResourceTool.class);
763 if (text == null) {
764 return null;
765 }
766 return text.get("template.lastpublished") + ": " + publishDate;
767 }
768
769
770
771
772 @Override
773 @Nonnull
774 public String getDatePosition() {
775
776
777
778 PositionType datePosition = PositionType.none;
779 final SiteModel model = this.getSiteModel();
780 if (model != null) {
781 final PublishDate publishDate = model.getPublishDate();
782 if (publishDate != null) {
783 final String position = publishDate.getPosition();
784 if (position != null && !position.isEmpty()) {
785 datePosition = PositionType.valueOfHyphen(position);
786 }
787 }
788 }
789 if (datePosition == null) {
790 datePosition = PositionType.none;
791 }
792 return datePosition.toString();
793 }
794
795
796
797
798 @Override
799 @Nonnull
800 public String getVersion() {
801 final MavenProject project = this.getProject();
802 return project.getVersion();
803 }
804
805
806
807
808 @Override
809 @Nonnull
810 public String getVersionPosition() {
811 final SiteModel model = this.getSiteModel();
812 if (model == null || model.getVersion() == null) {
813 return PositionType.none.toString();
814 }
815 PositionType position = PositionType.valueOfHyphen(model.getVersion().getPosition());
816 if (position == null) {
817 position = PositionType.none;
818 }
819 return position.toString();
820 }
821
822
823
824
825 @SuppressWarnings("null")
826 @Override
827 @Nonnull
828 public String getVersionMessage() {
829 final String version = getVersion();
830 if (Strings.isNullOrEmpty(version)) {
831 return null;
832 }
833 final ResourceTool text = this.getToolbox("text", ResourceTool.class);
834 if (text == null) {
835 return null;
836 }
837 return text.get("template.version") + ": " + version;
838 }
839
840
841
842
843 @Override
844 @Nonnull
845 public String getNamespace() {
846 return namespace;
847 }
848
849
850
851
852
853 @Override
854 @Nonnull
855 public String getBodyContent() {
856 return requireNonNull(getContextValue("bodyContent", String.class));
857 }
858
859
860
861
862 @Nonnull
863 public String getProjectLocation() {
864 String projectSiteLoc = getProject().getUrl();
865 if (!Strings.isNullOrEmpty(projectSiteLoc)) {
866
867 if (!projectSiteLoc.endsWith("/")) {
868 projectSiteLoc += "/";
869 }
870 } else {
871 LOGGER.warn("the property project.url is required");
872 projectSiteLoc = "https://localhost/";
873 }
874 return projectSiteLoc;
875 }
876
877
878
879
880
881
882
883
884
885 @Nonnull
886 public String getCurrentFileName() {
887 return (String) velocityContext.get("currentFileName");
888 }
889
890
891
892
893 @Nonnull
894 public String getCurrentFileLocation() {
895 final String projectSiteLoc = getProjectLocation();
896 return URITool.toURI(projectSiteLoc).resolve(getCurrentFileName()).toString();
897 }
898
899
900
901
902 @Override
903 @SuppressWarnings("unchecked")
904 @Nullable public <T> T eval(@Nullable final String vtl, @Nonnull final Class<T> requiredClass) {
905 if (vtl == null) {
906 return null;
907 }
908 final RenderTool renderTool = (RenderTool) getVelocityContext().get("render");
909 try {
910 return (T) renderTool.eval(getVelocityContext(), vtl);
911 } catch (final Exception ex) {
912 throw new RuntimeException("error when try evaluate '" + vtl + "'", ex);
913 }
914 }
915
916
917
918
919 @Override
920 public String relativeLink(final String href) {
921 if (href == null) {
922 return null;
923 }
924 if (isExternalLink(href)) {
925 return href;
926 }
927 final String relativePath = (String) velocityContext.get("relativePath");
928 String relativeLink = PathTool.calculateLink(href, relativePath);
929 relativeLink = relativeLink.replaceAll("\\\\", "/");
930 if (Strings.isNullOrEmpty(relativeLink)) {
931 relativeLink = "./";
932 }
933
934
935
936
937
938
939
940 final String currentFileLoc = getCurrentFileLocation();
941 final String absoluteLink = URITool.toURI(currentFileLoc).resolve(relativeLink).normalize().toString();
942 if (currentFileLoc.equals(absoluteLink)) {
943
944 relativeLink = StringUtils.EMPTY;
945 } else {
946
947
948 final String currentFileDir = PathTool.getDirectoryComponent(currentFileLoc);
949 relativeLink = URITool.relativizeLink(currentFileDir, absoluteLink);
950 }
951 if (LOGGER.isTraceEnabled()) {
952 LOGGER.trace("-- Relative Link ----------------------------------");
953 LOGGER.trace("link: {}", href);
954 LOGGER.trace("currentFileLoc: {}", currentFileLoc);
955 LOGGER.trace("absoluteLink: {}", absoluteLink);
956 LOGGER.trace("relativeLink: {}", relativeLink);
957 LOGGER.trace("---------------------------------------------------");
958 }
959 return relativeLink;
960 }
961
962
963
964
965
966
967 @SuppressWarnings("null")
968 @Override
969 public boolean isExternalLink(final String url) {
970 if (url == null) {
971 return false;
972 }
973 final String absoluteResourceURL = this.value("absoluteResourceURL");
974 if (!Strings.isNullOrEmpty(absoluteResourceURL) && url.startsWith(absoluteResourceURL)) {
975 return false;
976 }
977 return url.toLowerCase().startsWith("http:/") || url.toLowerCase().startsWith("https:/")
978 || url.toLowerCase().startsWith("ftp:/") || url.toLowerCase().startsWith("mailto:")
979 || url.toLowerCase().startsWith("file:/") || url.toLowerCase().indexOf("://") != -1;
980 }
981
982
983
984
985 @Override
986 public boolean isActiveLink(@Nullable final String href) {
987 final String alignedFileName = (String) velocityContext.get("alignedFileName");
988 if (href == null) {
989 return false;
990 }
991
992
993 return Strings.isNullOrEmpty(href) || alignedFileName.endsWith("index.html") && ".".equals(href);
994 }
995
996
997
998
999
1000
1001
1002
1003 @Nullable public static String slugFilename(@Nullable final String fileName) {
1004 if (fileName == null) {
1005 return null;
1006 }
1007 String currentFile = fileName;
1008
1009
1010 final int lastDot = currentFile.lastIndexOf(".");
1011 if (lastDot >= 0) {
1012 currentFile = currentFile.substring(0, lastDot);
1013 }
1014
1015
1016
1017
1018
1019
1020
1021 return HtmlTool.slug(currentFile.replace("/", "-").replace("\\", "-"));
1022 }
1023
1024
1025
1026
1027 @SuppressWarnings("null")
1028 @Override
1029 @Nonnull
1030 public String getResourcePath() {
1031 final String absoluteResourceURL = this.value("absoluteResourceURL");
1032 String projectUrl = getProjectLocation();
1033 final String currentFileName = getCurrentFileName();
1034 if (!Strings.isNullOrEmpty(projectUrl) && currentFileName != null) {
1035 if (projectUrl.charAt(projectUrl.length() - 1) != '/') {
1036 projectUrl += '/';
1037 }
1038 final String currentFileDir = URITool.toURI(projectUrl).resolve(currentFileName).resolve(".").toString();
1039 return URITool.relativizeLink(currentFileDir, absoluteResourceURL);
1040 }
1041 return (String) velocityContext.get("relativePath");
1042 }
1043
1044
1045
1046
1047
1048
1049
1050 @Override
1051 @Nullable public Date getBuildOutputTimestamp() throws ParseException {
1052 if (!this.velocityContext.containsKey(PROJECT_BUILD_OUTPUTTIMESTAMP)) {
1053 return null;
1054 }
1055 final Object outputTimestamp = this.velocityContext.get(PROJECT_BUILD_OUTPUTTIMESTAMP);
1056 if (outputTimestamp != null) {
1057 return ISO_8601BASIC_DATE.parse(outputTimestamp.toString());
1058 }
1059 return null;
1060 }
1061
1062 }