001package com.credibledoc.substitution.reporting.replacement; 002 003import com.credibledoc.combiner.file.FileService; 004import com.credibledoc.substitution.core.configuration.Configuration; 005import com.credibledoc.substitution.core.content.Content; 006import com.credibledoc.substitution.core.content.ContentGenerator; 007import com.credibledoc.substitution.core.content.ContentGeneratorService; 008import com.credibledoc.substitution.core.context.SubstitutionContext; 009import com.credibledoc.substitution.core.exception.SubstitutionRuntimeException; 010import com.credibledoc.substitution.core.pair.Pair; 011import com.credibledoc.substitution.core.placeholder.Placeholder; 012import com.credibledoc.substitution.core.placeholder.PlaceholderService; 013import com.credibledoc.substitution.core.resource.ResourceService; 014import com.credibledoc.substitution.core.resource.ResourceType; 015import com.credibledoc.substitution.core.resource.TemplateResource; 016import com.credibledoc.substitution.core.template.TemplateService; 017import com.credibledoc.substitution.core.tracking.Trackable; 018import com.credibledoc.substitution.reporting.markdown.MarkdownService; 019import com.credibledoc.substitution.reporting.reportdocument.creator.ReportDocumentCreator; 020import org.slf4j.Logger; 021import org.slf4j.LoggerFactory; 022 023import java.io.File; 024import java.io.FileOutputStream; 025import java.io.OutputStream; 026import java.nio.charset.StandardCharsets; 027import java.nio.file.Files; 028import java.nio.file.Path; 029import java.nio.file.Paths; 030import java.nio.file.StandardCopyOption; 031import java.util.ArrayList; 032import java.util.List; 033 034public class ReplacementService { 035 private static final Logger logger = LoggerFactory.getLogger(ReplacementService.class); 036 037 public static final String CONTENT_REPLACED = "Content replaced. "; 038 039 /** 040 * Singleton. 041 */ 042 private static final ReplacementService instance = new ReplacementService(); 043 044 /** 045 * @return The {@link ReplacementService} singleton. 046 */ 047 public static ReplacementService getInstance() { 048 return instance; 049 } 050 051 /** 052 * Load template from the {@link TemplateResource}, collect {@link Placeholder}s from the template, replace the 053 * {@link Placeholder}s with generated content and write the template with generated content to a target file. 054 * 055 * @param templateResource source of a template, for example <i>/template/markdown/doc/diagrams.md</i> 056 * @param substitutionContext the current state 057 */ 058 public void insertContentIntoTemplate(TemplateResource templateResource, SubstitutionContext substitutionContext) { 059 try { 060 List<String> templatePlaceholders = 061 PlaceholderService.getInstance().parsePlaceholders(templateResource, substitutionContext); 062 063 File generatedFile = getTargetFile(templateResource, substitutionContext); 064 065 if (templatePlaceholders.isEmpty()) { 066 Files.copy(templateResource.getFile().toPath(), generatedFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 067 return; 068 } 069 070 String replacedContent = 071 replaceContent(templateResource, templatePlaceholders, substitutionContext); 072 073 try (OutputStream outputStream = new FileOutputStream(generatedFile)){ 074 outputStream.write(replacedContent.getBytes()); 075 } 076 logger.trace("File generated. '{}'", generatedFile.getAbsolutePath()); 077 } catch (Exception exception) { 078 throw new SubstitutionRuntimeException(exception); 079 } 080 } 081 082 public File getTargetFile(TemplateResource templateResource, SubstitutionContext substitutionContext) { 083 ResourceService resourceService = ResourceService.getInstance(); 084 String placeholderResourceRelativePath = 085 resourceService.generatePlaceholderResourceRelativePath(templateResource, substitutionContext); 086 Configuration configuration = substitutionContext.getConfiguration(); 087 File generatedFile = new File(configuration.getTargetDirectory() + placeholderResourceRelativePath); 088 File generatedFileDirectory = generatedFile.getParentFile(); 089 createDirectoryIfNotExists(generatedFileDirectory); 090 return generatedFile; 091 } 092 093 private String replaceContent(TemplateResource templateResource, 094 List<String> templatePlaceholders, 095 SubstitutionContext substitutionContext) { 096 String replacedContent = 097 TemplateService.getInstance().getTemplateContent(templateResource, StandardCharsets.UTF_8.name()); 098 String lineEnding = FileService.findLineEnding(replacedContent); 099 int position = 1; 100 for (String templatePlaceholder : templatePlaceholders) { 101 Placeholder placeholder = PlaceholderService.getInstance() 102 .parseJsonFromPlaceholder(templatePlaceholder, templateResource, substitutionContext); 103 placeholder.setId(Integer.toString(position++)); 104 String contentForReplacement = generateContent(placeholder, substitutionContext); 105 String normalizedContent = contentForReplacement.replaceAll(FileService.ANY_LINE_ENDING, lineEnding); 106 replacedContent = replacedContent.replace(templatePlaceholder, normalizedContent); 107 String json = PlaceholderService.getInstance().writePlaceholderToJson(placeholder); 108 logger.trace("{}{}", CONTENT_REPLACED, json); 109 } 110 return replacedContent; 111 } 112 113 /** 114 * Depends on {@link Placeholder#getClassName()} use its instance for generation 115 * of placeholder content and replace it. 116 * 117 * @param placeholder contains a {@link Placeholder#getClassName()} 118 * @param substitutionContext the current state 119 * @return in case of {@link ReportDocumentCreator} return for example 120 * <pre></pre> 121 * tag. 122 * <p> 123 * In case of {@link ContentGenerator} return a generated content. 124 */ 125 @SuppressWarnings("unchecked") 126 private String generateContent(Placeholder placeholder, SubstitutionContext substitutionContext) { 127 try { 128 Class<?> placeholderClass = Class.forName(placeholder.getClassName()); 129 if (ReportDocumentCreator.class.isAssignableFrom(placeholderClass)) { 130 String generatedTag = findPlaceholderAndGenerateDiagram(placeholder, substitutionContext); 131 if (generatedTag != null) { 132 return generatedTag; 133 } 134 } else if (ContentGenerator.class.isAssignableFrom(placeholderClass)) { 135 return processContentGenerator(placeholder, 136 substitutionContext, 137 (Class<? extends ContentGenerator>) placeholderClass); 138 } 139 } catch (ClassNotFoundException classNotFoundException) { 140 throw new SubstitutionRuntimeException("PlaceholderClass cannot be found." + 141 " Placeholder className: '" + placeholder.getClassName() + 142 "', placeholder: " + placeholder, classNotFoundException); 143 } 144 throw new SubstitutionRuntimeException("Cannot generate a content " + 145 "for the placeholder: " + placeholder + "'"); 146 } 147 148 private String processContentGenerator(Placeholder placeholder, SubstitutionContext substitutionContext, 149 Class<? extends ContentGenerator> placeholderClass) { 150 ContentGenerator contentGenerator = 151 ContentGeneratorService.getInstance().getContentGenerator(placeholderClass); 152 153 Content content = contentGenerator.generate(placeholder, substitutionContext); 154 155 if (contentGenerator instanceof Trackable && !ResourceService.getInstance().isLocatedInJar()) { 156 Trackable trackable = (Trackable) contentGenerator; 157 List<Path> paths = trackable.getFragmentPaths(); 158 for (Path path : paths) { 159 Pair<Path, Path> pair = new Pair<>(path, placeholder.getResource().getFile().toPath()); 160 List<Pair<Path, Path>> pairs = substitutionContext.getTrackableRepository().getPairs(); 161 if (!pairs.contains(pair)) { 162 pairs.add(pair); 163 } 164 } 165 } 166 167 if (content.getPlantUmlContent() != null) { 168 String svg = MarkdownService.getInstance() 169 .generateDiagram(placeholder, content.getPlantUmlContent(), substitutionContext); 170 if (content.getMarkdownContent() != null) { 171 return svg + content.getMarkdownContent(); 172 } 173 return svg; 174 } else { 175 return content.getMarkdownContent(); 176 } 177 } 178 179 private String findPlaceholderAndGenerateDiagram(Placeholder placeholder, SubstitutionContext substitutionContext) { 180 for (Placeholder nextPlaceholder : substitutionContext.getPlaceholderRepository().getPlaceholders()) { 181 if (nextPlaceholder.getResource().equals(placeholder.getResource()) && 182 nextPlaceholder.getId().equals(placeholder.getId())) { 183 184 return MarkdownService.getInstance() 185 .generateDiagram(nextPlaceholder, null, substitutionContext); 186 } 187 } 188 return null; 189 } 190 191 private void createDirectoryIfNotExists(File directory) { 192 if (!directory.exists()) { 193 boolean created = directory.mkdirs(); 194 if (!created) { 195 throw new SubstitutionRuntimeException("Cannot create a new directory '" + 196 directory.getAbsolutePath() + "'"); 197 } 198 logger.info("The new directory created '{}'", directory.getAbsolutePath()); 199 } 200 } 201 202 /** 203 * Call the {@link #copyResourcesToTargetDirectory(SubstitutionContext)} method and then for every 204 * {@link TemplateResource} call the {@link #insertContentIntoTemplate(TemplateResource, SubstitutionContext)} method. 205 * @param substitutionContext the current state. 206 */ 207 public void replace(SubstitutionContext substitutionContext) { 208 List<TemplateResource> templateResources = copyResourcesToTargetDirectory(substitutionContext); 209 for (TemplateResource templateResource : templateResources) { 210 insertContentIntoTemplate(templateResource, substitutionContext); 211 } 212 } 213 214 public List<TemplateResource> copyResourcesToTargetDirectory(SubstitutionContext substitutionContext) { 215 try { 216 List<TemplateResource> result = new ArrayList<>(); 217 Configuration configuration = substitutionContext.getConfiguration(); 218 ResourceService resourceService = ResourceService.getInstance(); 219 List<TemplateResource> allResources = 220 resourceService.getResources(null, configuration.getTemplatesResource()); 221 TemplateService templateService = TemplateService.getInstance(); 222 PlaceholderService placeholderService = PlaceholderService.getInstance(); 223 logger.info("Templates will be copied to the target directory. Templates number: {}. " + 224 "Target directory: '{}'.", allResources.size(), configuration.getTargetDirectory()); 225 for (TemplateResource templateResource : allResources) { 226 List<String> placeholders = placeholderService.parsePlaceholders(templateResource, substitutionContext); 227 if (!placeholders.isEmpty()) { 228 result.add(templateResource); 229 } 230 if (templateResource.getType() == ResourceType.FILE) { 231 String targetFileRelativePath = 232 resourceService.generatePlaceholderResourceRelativePath(templateResource, substitutionContext); 233 String targetFileAbsolutePath = configuration.getTargetDirectory() + targetFileRelativePath; 234 logger.trace("Resource will be copied to file. Resource: '{}'. TargetFileAbsolutePath: '{}'", 235 templateResource, targetFileAbsolutePath); 236 Path targetPath = Paths.get(targetFileAbsolutePath); 237 Files.createDirectories(targetPath.getParent()); 238 Files.copy(templateResource.getFile().toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING); 239 } else if (templateResource.getType() == ResourceType.CLASSPATH) { 240 if (containsDotInName(templateResource.getPath())) { 241 String targetFileRelativePath = 242 resourceService.generatePlaceholderResourceRelativePath(templateResource, 243 substitutionContext); 244 String targetFileAbsolutePath = configuration.getTargetDirectory() + targetFileRelativePath; 245 logger.trace("Resource will be copied to file. Resource: '{}'. TargetFileAbsolutePath: '{}'", 246 templateResource, targetFileAbsolutePath); 247 File file = templateService.exportResource(templateResource.getPath(), targetFileAbsolutePath); 248 logger.trace("Resource copied to file: '{}'", file.getAbsolutePath()); 249 } 250 } else { 251 throw new IllegalArgumentException("Unknown ResourceType " + templateResource.getType()); 252 } 253 } 254 return result; 255 } catch (Exception e) { 256 throw new SubstitutionRuntimeException(e); 257 } 258 } 259 260 /** 261 * @param resource for example '/template/markdown/' is a directory, and '/template/markdown/README.md' is a file. 262 * @return 'False' if this resource is directory 263 */ 264 private boolean containsDotInName(String resource) { 265 int index = resource.lastIndexOf('/'); 266 if (index == -1) { 267 index = 0; 268 } 269 String fileName = resource.substring(index); 270 return fileName.contains("."); 271 } 272}