1 /******************************************************************************* 2 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors 3 * This program and the accompanying materials are made available under 4 * the terms of the Eclipse Public License 2.0 which is available at 5 * http://www.eclipse.org/legal/epl-2.0 6 * 7 * SPDX-License-Identifier: EPL-2.0 8 * 9 * Contributors: 10 * Marc R. Hoffmann - initial API and implementation 11 * 12 *******************************************************************************/ 13 package org.jacoco.report.internal; 14 15 import java.util.BitSet; 16 import java.util.HashMap; 17 import java.util.HashSet; 18 import java.util.Locale; 19 import java.util.Map; 20 import java.util.Set; 21 22 /** 23 * Internal utility to create normalized file names from string ids. The file 24 * names generated by an instance of this class have the following properties: 25 * 26 * <ul> 27 * <li>The same input id is mapped to the same file name.</li> 28 * <li>Different ids are mapped to different file names.</li> 29 * <li>For safe characters the file name corresponds to the input id, other 30 * characters are replaced by <code>_</code> (underscore).</li> 31 * <li>File names are case aware, i.e. the same file name but with different 32 * upper/lower case characters is not possible.</li> 33 * <li>If unique filenames can't directly created from the ids, additional 34 * suffixes are appended.</li> 35 * </ul> 36 */ 37 class NormalizedFileNames { 38 39 private static final BitSet LEGAL_CHARS = new BitSet(); 40 41 static { 42 final String legal = "abcdefghijklmnopqrstuvwxyz" 43 + "ABCDEFGHIJKLMNOPQRSTUVWYXZ0123456789$-._"; 44 for (final char c : legal.toCharArray()) { 45 LEGAL_CHARS.set(c); 46 } 47 } 48 49 private final Map<String, String> mapping = new HashMap<String, String>(); 50 51 private final Set<String> usedNames = new HashSet<String>(); 52 getFileName(final String id)53 public String getFileName(final String id) { 54 String name = mapping.get(id); 55 if (name != null) { 56 return name; 57 } 58 name = replaceIllegalChars(id); 59 name = ensureUniqueness(name); 60 mapping.put(id, name); 61 return name; 62 } 63 replaceIllegalChars(final String s)64 private String replaceIllegalChars(final String s) { 65 final StringBuilder sb = new StringBuilder(s.length()); 66 boolean modified = false; 67 for (int i = 0; i < s.length(); i++) { 68 final char c = s.charAt(i); 69 if (LEGAL_CHARS.get(c)) { 70 sb.append(c); 71 } else { 72 sb.append('_'); 73 modified = true; 74 } 75 } 76 return modified ? sb.toString() : s; 77 } 78 ensureUniqueness(final String s)79 private String ensureUniqueness(final String s) { 80 String unique = s; 81 String lower = unique.toLowerCase(Locale.ENGLISH); 82 int idx = 1; 83 while (usedNames.contains(lower)) { 84 unique = s + '~' + idx++; 85 lower = unique.toLowerCase(Locale.ENGLISH); 86 } 87 usedNames.add(lower); 88 return unique; 89 } 90 91 } 92