1 package org.unicode.cldr.icu; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.util.Collection; 6 import java.util.Collections; 7 import java.util.HashMap; 8 import java.util.HashSet; 9 import java.util.Iterator; 10 import java.util.List; 11 import java.util.Map; 12 import java.util.Map.Entry; 13 import java.util.Set; 14 15 import org.unicode.cldr.icu.ResourceSplitter.SplitInfo; 16 17 public class IcuDataSplitter { 18 private static final String VERSION_PATH = "/Version"; 19 private static final String PARENT_PATH = "/%%Parent"; 20 21 private final List<SplitInfo> splitInfos; 22 private final Map<String, File> targetDirs; 23 private final Map<String, Set<String>> splitSources = new HashMap<String, Set<String>>(); 24 25 /** 26 * Splits the 27 * 28 * @param splitInfos 29 */ IcuDataSplitter(List<SplitInfo> splitInfos)30 private IcuDataSplitter(List<SplitInfo> splitInfos) { 31 this.splitInfos = splitInfos; 32 targetDirs = new HashMap<String, File>(); 33 } 34 35 /** 36 * Creates a new IcuDataSplitter and creates directories for the split files 37 * if they do not already exist. 38 * 39 * @param mainDirPath 40 * the main directory that other directories will be relative to. 41 * @param splitInfos 42 * @return 43 */ make(String mainDirPath, List<SplitInfo> splitInfos)44 public static IcuDataSplitter make(String mainDirPath, List<SplitInfo> splitInfos) { 45 IcuDataSplitter splitter = new IcuDataSplitter(splitInfos); 46 // Make sure that all the required directories are present. 47 Map<String, File> targetDirs = splitter.targetDirs; 48 for (SplitInfo si : splitInfos) { 49 String dirPath = si.targetDirPath; 50 if (!targetDirs.containsKey(dirPath)) { 51 File dir = new File(dirPath); 52 if (!dir.isAbsolute()) { 53 dir = new File(mainDirPath, "/../" + dirPath); 54 } 55 if (dir.exists()) { 56 if (!dir.isDirectory()) { 57 throw new IllegalArgumentException( 58 "File \"" + dirPath + "\" exists and is not a directory"); 59 } 60 if (!dir.canWrite()) { 61 throw new IllegalArgumentException( 62 "Cannot write to directory \"" + dirPath + "\""); 63 } 64 } else { 65 if (!dir.mkdirs()) { 66 String canonicalPath; 67 try { 68 canonicalPath = dir.getCanonicalPath(); 69 } catch (IOException e) { 70 canonicalPath = dirPath; 71 } 72 throw new IllegalArgumentException( 73 "Unable to create directory path \"" + canonicalPath + "\""); 74 } 75 } 76 targetDirs.put(dirPath, dir); 77 } 78 } 79 return splitter; 80 } 81 82 /** 83 * Splits an IcuData object for writing to different directories. 84 * 85 * @param data 86 * @return 87 */ split(IcuData icuData, String fallbackDir)88 public Map<String, IcuData> split(IcuData icuData, String fallbackDir) { 89 Map<String, IcuData> splitData = new HashMap<String, IcuData>(); 90 String sourceFile = icuData.getSourceFile(); 91 String name = icuData.getName(); 92 boolean hasFallback = icuData.hasFallback(); 93 Set<String> dirs = targetDirs.keySet(); 94 for (String dir : dirs) { 95 splitData.put(dir, new IcuData(sourceFile, name, hasFallback)); 96 } 97 splitData.put(fallbackDir, new IcuData(sourceFile, name, hasFallback)); 98 99 for (Entry<String, List<String[]>> entry : icuData.entrySet()) { 100 String rbPath = entry.getKey(); 101 List<String[]> values = entry.getValue(); 102 boolean wasSplit = false; 103 // Paths that should be copied to all directories. 104 if (rbPath.equals(VERSION_PATH) || rbPath.equals(PARENT_PATH)) { 105 for (String dir : dirs) { 106 splitData.get(dir).addAll(rbPath, values); 107 } 108 } else { 109 // Split up regular paths. 110 for (SplitInfo splitInfo : splitInfos) { 111 String checkPath = rbPath.replaceFirst(":alias", "/"); // Handle splitting of a top level alias ( as in root/units ) 112 if (checkPath.startsWith(splitInfo.srcNodePath)) { 113 splitData.get(splitInfo.targetDirPath).addAll(rbPath, values); 114 wasSplit = true; 115 break; 116 } 117 } 118 } 119 // Add any remaining values to the file in fallback dir. 120 if (!wasSplit) { 121 splitData.get(fallbackDir).addAll(rbPath, values); 122 } 123 } 124 // Remove all files that only contain version info. 125 Iterator<Entry<String, IcuData>> iterator = splitData.entrySet().iterator(); 126 String comment = icuData.getFileComment(); 127 while (iterator.hasNext()) { 128 Entry<String, IcuData> entry = iterator.next(); 129 IcuData data = entry.getValue(); 130 data.setFileComment(comment); 131 if (entry.getKey().equals(fallbackDir)) continue; 132 if (data.size() == 1 && data.containsKey(VERSION_PATH)) { 133 // Comment copied from ResourceSplitter: 134 // Some locales that use root data rely on the presence of 135 // a resource file matching the prefix of the locale to prevent fallback 136 // lookup through the default locale. To prevent this error, all resources 137 // need at least a language-only stub resource to be present. 138 // 139 // Arrgh. The icu package tool wants all internal nodes in the tree to be 140 // present. Currently, the missing nodes are all lang_Script locales. 141 // Maybe change the package tool to fix this. 142 String locale = data.getName(); 143 int underscorePos = locale.indexOf('_'); 144 if (underscorePos > -1 && locale.length() - underscorePos - 1 != 4) { 145 iterator.remove(); 146 continue; 147 } 148 } 149 add(splitSources, entry.getKey(), data.getName()); 150 } 151 return splitData; 152 } 153 154 /** 155 * Adds a value to the list with the specified key. 156 */ add(Map<String, Set<String>> map, String key, String value)157 private static void add(Map<String, Set<String>> map, String key, String value) { 158 Set<String> set = map.get(key); 159 if (set == null) { 160 map.put(key, set = new HashSet<String>()); 161 } 162 set.add(value); 163 } 164 165 /** 166 * Returns the set of directories that the splitter splits data into (excluding the main directory). 167 */ getTargetDirs()168 public Set<String> getTargetDirs() { 169 return targetDirs.keySet(); 170 } 171 getDirSources(String dir)172 public Set<String> getDirSources(String dir) { 173 return Collections.unmodifiableSet(splitSources.get(dir)); 174 } 175 generateMakefile(Collection<String> aliases, String dir)176 public Makefile generateMakefile(Collection<String> aliases, String dir) { 177 String prefix = dir.toUpperCase(); 178 Makefile makefile = new Makefile(prefix); 179 makefile.addSyntheticAlias(aliases); 180 makefile.addAliasSource(); 181 makefile.addSource(splitSources.get(dir)); 182 return makefile; 183 } 184 } 185