1/* 2 * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.javadoc.internal.doclets.toolkit.util; 27 28import java.util.*; 29 30import javax.lang.model.element.ModuleElement; 31import javax.lang.model.element.PackageElement; 32 33import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; 34import jdk.javadoc.internal.doclets.toolkit.Messages; 35 36 37/** 38 * Process and manage grouping of elements, as specified by "-group" option on 39 * the command line. 40 * <p> 41 * For example, if user has used -group option as 42 * -group "Core Packages" "java.*" -group "CORBA Packages" "org.omg.*", then 43 * the packages specified on the command line will be grouped according to their 44 * names starting with either "java." or "org.omg.". All the other packages 45 * which do not fall in the user given groups, are grouped in default group, 46 * named as either "Other Packages" or "Packages" depending upon if "-group" 47 * option used or not at all used respectively. 48 * </p> 49 * <p> 50 * Also the packages are grouped according to the longest possible match of 51 * their names with the grouping information provided. For example, if there 52 * are two groups, like -group "Lang" "java.lang" and -group "Core" "java.*", 53 * will put the package java.lang in the group "Lang" and not in group "Core". 54 * </p> 55 * 56 * <p><b>This is NOT part of any supported API. 57 * If you write code that depends on this, you do so at your own risk. 58 * This code and its internal interfaces are subject to change or 59 * deletion without notice.</b> 60 * 61 * @author Atul M Dambalkar 62 */ 63public class Group { 64 65 /** 66 * Map of regular expressions with the corresponding group name. 67 */ 68 private Map<String,String> regExpGroupMap = new HashMap<>(); 69 70 /** 71 * List of regular expressions sorted according to the length. Regular 72 * expression with longest length will be first in the sorted order. 73 */ 74 private List<String> sortedRegExpList = new ArrayList<>(); 75 76 /** 77 * List of group names in the same order as given on the command line. 78 */ 79 private List<String> groupList = new ArrayList<>(); 80 81 /** 82 * Map of non-regular expressions(possible package or module names) with the 83 * corresponding group name. 84 */ 85 private Map<String,String> elementNameGroupMap = new HashMap<>(); 86 87 /** 88 * The global configuration information for this run. 89 */ 90 private final BaseConfiguration configuration; 91 private Messages messages; 92 93 /** 94 * Since we need to sort the keys in the reverse order(longest key first), 95 * the compare method in the implementing class is doing the reverse 96 * comparison. 97 */ 98 private static class MapKeyComparator implements Comparator<String> { 99 public int compare(String key1, String key2) { 100 return key2.length() - key1.length(); 101 } 102 } 103 104 public Group(BaseConfiguration configuration) { 105 this.configuration = configuration; 106 messages = configuration.getMessages(); 107 } 108 109 /** 110 * Depending upon the format of the module name provided in the "-group" 111 * option, generate two separate maps. There will be a map for mapping 112 * regular expression(only meta character allowed is '*' and that is at the 113 * end of the regular expression) on to the group name. And another map 114 * for mapping (possible) module names(if the name format doesn't contain 115 * meta character '*', then it is assumed to be a module name) on to the 116 * group name. This will also sort all the regular expressions found in the 117 * reverse order of their lengths, i.e. longest regular expression will be 118 * first in the sorted list. 119 * 120 * @param groupname The name of the group from -group option. 121 * @param moduleNameFormList List of the module name formats. 122 */ 123 public boolean checkModuleGroups(String groupname, String moduleNameFormList) { 124 String[] mdlPatterns = moduleNameFormList.split(":"); 125 if (groupList.contains(groupname)) { 126 initMessages(); 127 messages.warning("doclet.Groupname_already_used", groupname); 128 return false; 129 } 130 groupList.add(groupname); 131 for (String mdlPattern : mdlPatterns) { 132 if (mdlPattern.length() == 0) { 133 initMessages(); 134 messages.warning("doclet.Error_in_grouplist", groupname, moduleNameFormList); 135 return false; 136 } 137 if (mdlPattern.endsWith("*")) { 138 mdlPattern = mdlPattern.substring(0, mdlPattern.length() - 1); 139 if (foundGroupFormat(regExpGroupMap, mdlPattern)) { 140 return false; 141 } 142 regExpGroupMap.put(mdlPattern, groupname); 143 sortedRegExpList.add(mdlPattern); 144 } else { 145 if (foundGroupFormat(elementNameGroupMap, mdlPattern)) { 146 return false; 147 } 148 elementNameGroupMap.put(mdlPattern, groupname); 149 } 150 } 151 Collections.sort(sortedRegExpList, new MapKeyComparator()); 152 return true; 153 } 154 155 /** 156 * Depending upon the format of the package name provided in the "-group" 157 * option, generate two separate maps. There will be a map for mapping 158 * regular expression(only meta character allowed is '*' and that is at the 159 * end of the regular expression) on to the group name. And another map 160 * for mapping (possible) package names(if the name format doesn't contain 161 * meta character '*', then it is assumed to be a package name) on to the 162 * group name. This will also sort all the regular expressions found in the 163 * reverse order of their lengths, i.e. longest regular expression will be 164 * first in the sorted list. 165 * 166 * @param groupname The name of the group from -group option. 167 * @param pkgNameFormList List of the package name formats. 168 */ 169 public boolean checkPackageGroups(String groupname, String pkgNameFormList) { 170 String[] pkgPatterns = pkgNameFormList.split(":"); 171 if (groupList.contains(groupname)) { 172 initMessages(); 173 messages.warning("doclet.Groupname_already_used", groupname); 174 return false; 175 } 176 groupList.add(groupname); 177 for (String pkgPattern : pkgPatterns) { 178 if (pkgPattern.length() == 0) { 179 initMessages(); 180 messages.warning("doclet.Error_in_grouplist", groupname, pkgNameFormList); 181 return false; 182 } 183 if (pkgPattern.endsWith("*")) { 184 pkgPattern = pkgPattern.substring(0, pkgPattern.length() - 1); 185 if (foundGroupFormat(regExpGroupMap, pkgPattern)) { 186 return false; 187 } 188 regExpGroupMap.put(pkgPattern, groupname); 189 sortedRegExpList.add(pkgPattern); 190 } else { 191 if (foundGroupFormat(elementNameGroupMap, pkgPattern)) { 192 return false; 193 } 194 elementNameGroupMap.put(pkgPattern, groupname); 195 } 196 } 197 Collections.sort(sortedRegExpList, new MapKeyComparator()); 198 return true; 199 } 200 201 // Lazy init of the messages for now, because Group is created 202 // in BaseConfiguration before configuration is fully initialized. 203 private void initMessages() { 204 if (messages == null) { 205 messages = configuration.getMessages(); 206 } 207 } 208 209 /** 210 * Search if the given map has the given element format. 211 * 212 * @param map Map to be searched. 213 * @param elementFormat The format to search. 214 * 215 * @return true if element name format found in the map, else false. 216 */ 217 boolean foundGroupFormat(Map<String,?> map, String elementFormat) { 218 if (map.containsKey(elementFormat)) { 219 initMessages(); 220 messages.error("doclet.Same_element_name_used", elementFormat); 221 return true; 222 } 223 return false; 224 } 225 226 /** 227 * Group the modules according the grouping information provided on the 228 * command line. Given a list of modules, search each module name in 229 * regular expression map as well as module name map to get the 230 * corresponding group name. Create another map with mapping of group name 231 * to the module list, which will fall under the specified group. If any 232 * module doesn't belong to any specified group on the command line, then 233 * a new group named "Other Modules" will be created for it. If there are 234 * no groups found, in other words if "-group" option is not at all used, 235 * then all the modules will be grouped under group "Modules". 236 * 237 * @param modules Specified modules. 238 * @return map of group names and set of module elements. 239 */ 240 public Map<String, SortedSet<ModuleElement>> groupModules(Set<ModuleElement> modules) { 241 Map<String, SortedSet<ModuleElement>> groupModuleMap = new HashMap<>(); 242 String defaultGroupName = 243 (elementNameGroupMap.isEmpty() && regExpGroupMap.isEmpty())? 244 configuration.getResources().getText("doclet.Modules") : 245 configuration.getResources().getText("doclet.Other_Modules"); 246 // if the user has not used the default group name, add it 247 if (!groupList.contains(defaultGroupName)) { 248 groupList.add(defaultGroupName); 249 } 250 for (ModuleElement mdl : modules) { 251 String moduleName = mdl.isUnnamed() ? null : mdl.getQualifiedName().toString(); 252 String groupName = mdl.isUnnamed() ? null : elementNameGroupMap.get(moduleName); 253 // if this module is not explicitly assigned to a group, 254 // try matching it to group specified by regular expression 255 if (groupName == null) { 256 groupName = regExpGroupName(moduleName); 257 } 258 // if it is in neither group map, put it in the default 259 // group 260 if (groupName == null) { 261 groupName = defaultGroupName; 262 } 263 getModuleList(groupModuleMap, groupName).add(mdl); 264 } 265 return groupModuleMap; 266 } 267 268 /** 269 * Group the packages according the grouping information provided on the 270 * command line. Given a list of packages, search each package name in 271 * regular expression map as well as package name map to get the 272 * corresponding group name. Create another map with mapping of group name 273 * to the package list, which will fall under the specified group. If any 274 * package doesn't belong to any specified group on the command line, then 275 * a new group named "Other Packages" will be created for it. If there are 276 * no groups found, in other words if "-group" option is not at all used, 277 * then all the packages will be grouped under group "Packages". 278 * 279 * @param packages Packages specified on the command line. 280 * @return map of group names and set of package elements 281 */ 282 public Map<String, SortedSet<PackageElement>> groupPackages(Set<PackageElement> packages) { 283 Map<String, SortedSet<PackageElement>> groupPackageMap = new HashMap<>(); 284 String defaultGroupName = 285 (elementNameGroupMap.isEmpty() && regExpGroupMap.isEmpty())? 286 configuration.getResources().getText("doclet.Packages") : 287 configuration.getResources().getText("doclet.Other_Packages"); 288 // if the user has not used the default group name, add it 289 if (!groupList.contains(defaultGroupName)) { 290 groupList.add(defaultGroupName); 291 } 292 for (PackageElement pkg : packages) { 293 String pkgName = pkg.isUnnamed() ? null : configuration.utils.getPackageName(pkg); 294 String groupName = pkg.isUnnamed() ? null : elementNameGroupMap.get(pkgName); 295 // if this package is not explicitly assigned to a group, 296 // try matching it to group specified by regular expression 297 if (groupName == null) { 298 groupName = regExpGroupName(pkgName); 299 } 300 // if it is in neither group map, put it in the default 301 // group 302 if (groupName == null) { 303 groupName = defaultGroupName; 304 } 305 getPkgList(groupPackageMap, groupName).add(pkg); 306 } 307 return groupPackageMap; 308 } 309 310 /** 311 * Search for element name in the sorted regular expression 312 * list, if found return the group name. If not, return null. 313 * 314 * @param elementName Name of element to be found in the regular 315 * expression list. 316 */ 317 String regExpGroupName(String elementName) { 318 for (String regexp : sortedRegExpList) { 319 if (elementName.startsWith(regexp)) { 320 return regExpGroupMap.get(regexp); 321 } 322 } 323 return null; 324 } 325 326 /** 327 * For the given group name, return the package list, on which it is mapped. 328 * Create a new list, if not found. 329 * 330 * @param map Map to be searched for group name. 331 * @param groupname Group name to search. 332 */ 333 SortedSet<PackageElement> getPkgList(Map<String, SortedSet<PackageElement>> map, 334 String groupname) { 335 return map.computeIfAbsent(groupname, g -> new TreeSet<>(configuration.utils.makePackageComparator())); 336 } 337 338 /** 339 * For the given group name, return the module list, on which it is mapped. 340 * Create a new list, if not found. 341 * 342 * @param map Map to be searched for group name. 343 * @param groupname Group name to search. 344 */ 345 SortedSet<ModuleElement> getModuleList(Map<String, SortedSet<ModuleElement>> map, 346 String groupname) { 347 return map.computeIfAbsent(groupname, g -> new TreeSet<>(configuration.utils.makeModuleComparator())); 348 } 349 350 /** 351 * Return the list of groups, in the same order as specified 352 * on the command line. 353 */ 354 public List<String> getGroupList() { 355 return groupList; 356 } 357} 358