001/* 002 * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit.util; 027 028import java.util.*; 029 030import javax.lang.model.element.ModuleElement; 031import javax.lang.model.element.PackageElement; 032 033import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 034import org.jdrupes.mdoclet.internal.doclets.toolkit.Messages; 035 036/** 037 * Process and manage grouping of elements, as specified by "-group" option on 038 * the command line. 039 * <p> 040 * For example, if user has used -group option as 041 * -group "Core Packages" "java.*" -group "CORBA Packages" "org.omg.*", then 042 * the packages specified on the command line will be grouped according to their 043 * names starting with either "java." or "org.omg.". All the other packages 044 * which do not fall in the user given groups, are grouped in default group, 045 * named as either "Other Packages" or "Packages" depending upon if "-group" 046 * option used or not at all used respectively. 047 * </p> 048 * <p> 049 * Also the packages are grouped according to the longest possible match of 050 * their names with the grouping information provided. For example, if there 051 * are two groups, like -group "Lang" "java.lang" and -group "Core" "java.*", 052 * will put the package java.lang in the group "Lang" and not in group "Core". 053 * </p> 054 */ 055public class Group { 056 057 /** 058 * Map of regular expressions with the corresponding group name. 059 */ 060 private Map<String, String> regExpGroupMap = new HashMap<>(); 061 062 /** 063 * List of regular expressions sorted according to the length. Regular 064 * expression with longest length will be first in the sorted order. 065 */ 066 private List<String> sortedRegExpList = new ArrayList<>(); 067 068 /** 069 * List of group names in the same order as given on the command line. 070 */ 071 private List<String> groupList = new ArrayList<>(); 072 073 /** 074 * Map of non-regular expressions(possible package or module names) with the 075 * corresponding group name. 076 */ 077 private Map<String, String> elementNameGroupMap = new HashMap<>(); 078 079 /** 080 * The global configuration information for this run. 081 */ 082 private final BaseConfiguration configuration; 083 private Messages messages; 084 085 /** 086 * Since we need to sort the keys in the reverse order(longest key first), 087 * the compare method in the implementing class is doing the reverse 088 * comparison. 089 */ 090 private static class MapKeyComparator implements Comparator<String> { 091 @Override 092 public int compare(String key1, String key2) { 093 return key2.length() - key1.length(); 094 } 095 } 096 097 public Group(BaseConfiguration configuration) { 098 this.configuration = configuration; 099 messages = configuration.getMessages(); 100 } 101 102 /** 103 * Depending upon the format of the module name provided in the "-group" 104 * option, generate two separate maps. There will be a map for mapping 105 * regular expression(only meta character allowed is '*' and that is at the 106 * end of the regular expression) on to the group name. And another map 107 * for mapping (possible) module names(if the name format doesn't contain 108 * meta character '*', then it is assumed to be a module name) on to the 109 * group name. This will also sort all the regular expressions found in the 110 * reverse order of their lengths, i.e. longest regular expression will be 111 * first in the sorted list. 112 * 113 * @param groupname The name of the group from -group option. 114 * @param moduleNameFormList List of the module name formats. 115 */ 116 public boolean checkModuleGroups(String groupname, 117 String moduleNameFormList) { 118 String[] mdlPatterns = moduleNameFormList.split("[,:]"); 119 if (groupList.contains(groupname)) { 120 initMessages(); 121 messages.warning("doclet.Groupname_already_used", groupname); 122 return false; 123 } 124 groupList.add(groupname); 125 for (String mdlPattern : mdlPatterns) { 126 if (mdlPattern.length() == 0) { 127 initMessages(); 128 messages.warning("doclet.Error_in_grouplist", groupname, 129 moduleNameFormList); 130 return false; 131 } 132 if (mdlPattern.endsWith("*")) { 133 mdlPattern = mdlPattern.substring(0, mdlPattern.length() - 1); 134 if (foundGroupFormat(regExpGroupMap, mdlPattern)) { 135 return false; 136 } 137 regExpGroupMap.put(mdlPattern, groupname); 138 sortedRegExpList.add(mdlPattern); 139 } else { 140 if (foundGroupFormat(elementNameGroupMap, mdlPattern)) { 141 return false; 142 } 143 elementNameGroupMap.put(mdlPattern, groupname); 144 } 145 } 146 sortedRegExpList.sort(new MapKeyComparator()); 147 return true; 148 } 149 150 /** 151 * Depending upon the format of the package name provided in the "-group" 152 * option, generate two separate maps. There will be a map for mapping 153 * regular expression(only meta character allowed is '*' and that is at the 154 * end of the regular expression) on to the group name. And another map 155 * for mapping (possible) package names(if the name format doesn't contain 156 * meta character '*', then it is assumed to be a package name) on to the 157 * group name. This will also sort all the regular expressions found in the 158 * reverse order of their lengths, i.e. longest regular expression will be 159 * first in the sorted list. 160 * 161 * @param groupname The name of the group from -group option. 162 * @param pkgNameFormList List of the package name formats. 163 */ 164 public boolean checkPackageGroups(String groupname, 165 String pkgNameFormList) { 166 String[] pkgPatterns = pkgNameFormList.split("[,:]"); 167 if (groupList.contains(groupname)) { 168 initMessages(); 169 messages.warning("doclet.Groupname_already_used", groupname); 170 return false; 171 } 172 groupList.add(groupname); 173 for (String pkgPattern : pkgPatterns) { 174 if (pkgPattern.length() == 0) { 175 initMessages(); 176 messages.warning("doclet.Error_in_grouplist", groupname, 177 pkgNameFormList); 178 return false; 179 } 180 if (pkgPattern.endsWith("*")) { 181 pkgPattern = pkgPattern.substring(0, pkgPattern.length() - 1); 182 if (foundGroupFormat(regExpGroupMap, pkgPattern)) { 183 return false; 184 } 185 regExpGroupMap.put(pkgPattern, groupname); 186 sortedRegExpList.add(pkgPattern); 187 } else { 188 if (foundGroupFormat(elementNameGroupMap, pkgPattern)) { 189 return false; 190 } 191 elementNameGroupMap.put(pkgPattern, groupname); 192 } 193 } 194 sortedRegExpList.sort(new MapKeyComparator()); 195 return true; 196 } 197 198 // Lazy init of the messages for now, because Group is created 199 // in BaseConfiguration before configuration is fully initialized. 200 private void initMessages() { 201 if (messages == null) { 202 messages = configuration.getMessages(); 203 } 204 } 205 206 /** 207 * Search if the given map has the given element format. 208 * 209 * @param map Map to be searched. 210 * @param elementFormat The format to search. 211 * 212 * @return true if element name format found in the map, else false. 213 */ 214 boolean foundGroupFormat(Map<String, ?> map, String elementFormat) { 215 if (map.containsKey(elementFormat)) { 216 initMessages(); 217 messages.error("doclet.Same_element_name_used", elementFormat); 218 return true; 219 } 220 return false; 221 } 222 223 /** 224 * Group the modules according the grouping information provided on the 225 * command line. Given a list of modules, search each module name in 226 * regular expression map as well as module name map to get the 227 * corresponding group name. Create another map with mapping of group name 228 * to the module list, which will fall under the specified group. If any 229 * module doesn't belong to any specified group on the command line, then 230 * a new group named "Other Modules" will be created for it. If there are 231 * no groups found, in other words if "-group" option is not at all used, 232 * then all the modules will be grouped under group "Modules". 233 * 234 * @param modules Specified modules. 235 * @return map of group names and set of module elements. 236 */ 237 public Map<String, SortedSet<ModuleElement>> 238 groupModules(Set<ModuleElement> modules) { 239 Map<String, SortedSet<ModuleElement>> groupModuleMap = new HashMap<>(); 240 String defaultGroupName 241 = (elementNameGroupMap.isEmpty() && regExpGroupMap.isEmpty()) 242 ? configuration.getDocResources().getText("doclet.Modules") 243 : configuration.getDocResources() 244 .getText("doclet.Other_Modules"); 245 // if the user has not used the default group name, add it 246 if (!groupList.contains(defaultGroupName)) { 247 groupList.add(defaultGroupName); 248 } 249 for (ModuleElement mdl : modules) { 250 String moduleName 251 = mdl.isUnnamed() ? null : mdl.getQualifiedName().toString(); 252 String groupName 253 = mdl.isUnnamed() ? null : elementNameGroupMap.get(moduleName); 254 // if this module is not explicitly assigned to a group, 255 // try matching it to group specified by regular expression 256 if (groupName == null) { 257 groupName = regExpGroupName(moduleName); 258 } 259 // if it is in neither group map, put it in the default 260 // group 261 if (groupName == null) { 262 groupName = defaultGroupName; 263 } 264 getModuleList(groupModuleMap, groupName).add(mdl); 265 } 266 return groupModuleMap; 267 } 268 269 /** 270 * Group the packages according the grouping information provided on the 271 * command line. Given a list of packages, search each package name in 272 * regular expression map as well as package name map to get the 273 * corresponding group name. Create another map with mapping of group name 274 * to the package list, which will fall under the specified group. If any 275 * package doesn't belong to any specified group on the command line, then 276 * a new group named "Other Packages" will be created for it. If there are 277 * no groups found, in other words if "-group" option is not at all used, 278 * then all the packages will be grouped under group "Packages". 279 * 280 * @param packages Packages specified on the command line. 281 * @return map of group names and set of package elements 282 */ 283 public Map<String, SortedSet<PackageElement>> 284 groupPackages(Set<PackageElement> packages) { 285 Map<String, SortedSet<PackageElement>> groupPackageMap 286 = new HashMap<>(); 287 String defaultGroupName 288 = (elementNameGroupMap.isEmpty() && regExpGroupMap.isEmpty()) 289 ? configuration.getDocResources().getText("doclet.Packages") 290 : configuration.getDocResources() 291 .getText("doclet.Other_Packages"); 292 // if the user has not used the default group name, add it 293 if (!groupList.contains(defaultGroupName)) { 294 groupList.add(defaultGroupName); 295 } 296 for (PackageElement pkg : packages) { 297 String pkgName = configuration.utils.getPackageName(pkg); 298 String groupName 299 = pkg.isUnnamed() ? null : elementNameGroupMap.get(pkgName); 300 // if this package is not explicitly assigned to a group, 301 // try matching it to group specified by regular expression 302 if (groupName == null) { 303 groupName = regExpGroupName(pkgName); 304 } 305 // if it is in neither group map, put it in the default 306 // group 307 if (groupName == null) { 308 groupName = defaultGroupName; 309 } 310 getPkgList(groupPackageMap, groupName).add(pkg); 311 } 312 return groupPackageMap; 313 } 314 315 /** 316 * Search for element name in the sorted regular expression 317 * list, if found return the group name. If not, return null. 318 * 319 * @param elementName Name of element to be found in the regular 320 * expression list. 321 */ 322 String regExpGroupName(String elementName) { 323 for (String regexp : sortedRegExpList) { 324 if (elementName.startsWith(regexp)) { 325 return regExpGroupMap.get(regexp); 326 } 327 } 328 return null; 329 } 330 331 /** 332 * For the given group name, return the package list, on which it is mapped. 333 * Create a new list, if not found. 334 * 335 * @param map Map to be searched for group name. 336 * @param groupname Group name to search. 337 */ 338 SortedSet<PackageElement> getPkgList( 339 Map<String, SortedSet<PackageElement>> map, 340 String groupname) { 341 return map.computeIfAbsent(groupname, g -> new TreeSet<>( 342 configuration.utils.comparators.makePackageComparator())); 343 } 344 345 /** 346 * For the given group name, return the module list, on which it is mapped. 347 * Create a new list, if not found. 348 * 349 * @param map Map to be searched for group name. 350 * @param groupname Group name to search. 351 */ 352 SortedSet<ModuleElement> getModuleList( 353 Map<String, SortedSet<ModuleElement>> map, 354 String groupname) { 355 return map.computeIfAbsent(groupname, g -> new TreeSet<>( 356 configuration.utils.comparators.makeModuleComparator())); 357 } 358 359 /** 360 * Return the list of groups, in the same order as specified 361 * on the command line. 362 */ 363 public List<String> getGroupList() { 364 return groupList; 365 } 366}