001    /*
002     * Copyright 1998-2008 Sun Microsystems, Inc.  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.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.doclets.internal.toolkit.util;
027    
028    import com.sun.tools.doclets.internal.toolkit.*;
029    import com.sun.javadoc.*;
030    import java.util.*;
031    
032    /**
033     * Process and manage grouping of packages, as specified by "-group" option on
034     * the command line.
035     * <p>
036     * For example, if user has used -group option as
037     * -group "Core Packages" "java.*" -group "CORBA Packages" "org.omg.*", then
038     * the packages specified on the command line will be grouped according to their
039     * names starting with either "java." or "org.omg.". All the other packages
040     * which do not fall in the user given groups, are grouped in default group,
041     * named as either "Other Packages" or "Packages" depending upon if "-group"
042     * option used or not at all used respectively.
043     * </p>
044     * <p>
045     * Also the packages are grouped according to the longest possible match of
046     * their names with the grouping information provided. For example, if there
047     * are two groups, like -group "Lang" "java.lang" and -group "Core" "java.*",
048     * will put the package java.lang in the group "Lang" and not in group "Core".
049     * </p>
050     *
051     * This code is not part of an API.
052     * It is implementation that is subject to change.
053     * Do not use it as an API
054     *
055     * @author Atul M Dambalkar
056     */
057    public class Group {
058    
059        /**
060         * Map of regular expressions with the corresponding group name.
061         */
062        private Map<String,String> regExpGroupMap = new HashMap<String,String>();
063    
064        /**
065         * List of regular expressions sorted according to the length. Regular
066         * expression with longest length will be first in the sorted order.
067         */
068        private List<String> sortedRegExpList = new ArrayList<String>();
069    
070        /**
071         * List of group names in the same order as given on the command line.
072         */
073        private List<String> groupList = new ArrayList<String>();
074    
075        /**
076         * Map of non-regular expressions(possible package names) with the
077         * corresponding group name.
078         */
079        private Map<String,String> pkgNameGroupMap = new HashMap<String,String>();
080    
081        /**
082         * The global configuration information for this run.
083         */
084        private final Configuration configuration;
085    
086        /**
087         * Since we need to sort the keys in the reverse order(longest key first),
088         * the compare method in the implementing class is doing the reverse
089         * comparison.
090         */
091        private static class MapKeyComparator implements Comparator<String> {
092            public int compare(String key1, String key2) {
093                return key2.length() - key1.length();
094            }
095        }
096    
097        public Group(Configuration configuration) {
098            this.configuration = configuration;
099        }
100    
101        /**
102         * Depending upon the format of the package name provided in the "-group"
103         * option, generate two separate maps. There will be a map for mapping
104         * regular expression(only meta character allowed is '*' and that is at the
105         * end of the regular expression) on to the group name. And another map
106         * for mapping (possible) package names(if the name format doesen't contain
107         * meta character '*', then it is assumed to be a package name) on to the
108         * group name. This will also sort all the regular expressions found in the
109         * reverse order of their lengths, i.e. longest regular expression will be
110         * first in the sorted list.
111         *
112         * @param groupname       The name of the group from -group option.
113         * @param pkgNameFormList List of the package name formats.
114         */
115        public boolean checkPackageGroups(String groupname,
116                String pkgNameFormList) {
117            StringTokenizer strtok = new StringTokenizer(pkgNameFormList, ":");
118            if (groupList.contains(groupname)) {
119                configuration.message.warning("doclet.Groupname_already_used", groupname);
120                return false;
121            }
122            groupList.add(groupname);
123            while (strtok.hasMoreTokens()) {
124                String id = strtok.nextToken();
125                if (id.length() == 0) {
126                    configuration.message.warning("doclet.Error_in_packagelist", groupname, pkgNameFormList);
127                    return false;
128                }
129                if (id.endsWith("*")) {
130                    id = id.substring(0, id.length() - 1);
131                    if (foundGroupFormat(regExpGroupMap, id)) {
132                        return false;
133                    }
134                    regExpGroupMap.put(id, groupname);
135                    sortedRegExpList.add(id);
136                } else {
137                    if (foundGroupFormat(pkgNameGroupMap, id)) {
138                        return false;
139                    }
140                    pkgNameGroupMap.put(id, groupname);
141                }
142            }
143            Collections.sort(sortedRegExpList, new MapKeyComparator());
144            return true;
145        }
146    
147        /**
148         * Search if the given map has given the package format.
149         *
150         * @param map Map to be searched.
151         * @param pkgFormat The pacakge format to search.
152         *
153         * @return true if package name format found in the map, else false.
154         */
155        boolean foundGroupFormat(Map<String,?> map, String pkgFormat) {
156            if (map.containsKey(pkgFormat)) {
157                configuration.message.error("doclet.Same_package_name_used", pkgFormat);
158                return true;
159            }
160            return false;
161        }
162    
163        /**
164         * Group the packages according the grouping information provided on the
165         * command line. Given a list of packages, search each package name in
166         * regular expression map as well as package name map to get the
167         * corresponding group name. Create another map with mapping of group name
168         * to the package list, which will fall under the specified group. If any
169         * package doesen't belong to any specified group on the comamnd line, then
170         * a new group named "Other Packages" will be created for it. If there are
171         * no groups found, in other words if "-group" option is not at all used,
172         * then all the packages will be grouped under group "Packages".
173         *
174         * @param packages Packages specified on the command line.
175         */
176        public Map<String,List<PackageDoc>> groupPackages(PackageDoc[] packages) {
177            Map<String,List<PackageDoc>> groupPackageMap = new HashMap<String,List<PackageDoc>>();
178            String defaultGroupName =
179                (pkgNameGroupMap.isEmpty() && regExpGroupMap.isEmpty())?
180                    configuration.message.getText("doclet.Packages") :
181                    configuration.message.getText("doclet.Other_Packages");
182            // if the user has not used the default group name, add it
183            if (!groupList.contains(defaultGroupName)) {
184                groupList.add(defaultGroupName);
185            }
186            for (int i = 0; i < packages.length; i++) {
187                PackageDoc pkg = packages[i];
188                String pkgName = pkg.name();
189                String groupName = pkgNameGroupMap.get(pkgName);
190                // if this package is not explicitly assigned to a group,
191                // try matching it to group specified by regular expression
192                if (groupName == null) {
193                    groupName = regExpGroupName(pkgName);
194                }
195                // if it is in neither group map, put it in the default
196                // group
197                if (groupName == null) {
198                    groupName = defaultGroupName;
199                }
200                getPkgList(groupPackageMap, groupName).add(pkg);
201            }
202            return groupPackageMap;
203        }
204    
205        /**
206         * Search for package name in the sorted regular expression
207         * list, if found return the group name.  If not, return null.
208         *
209         * @param pkgName Name of package to be found in the regular
210         * expression list.
211         */
212        String regExpGroupName(String pkgName) {
213            for (int j = 0; j < sortedRegExpList.size(); j++) {
214                String regexp = sortedRegExpList.get(j);
215                if (pkgName.startsWith(regexp)) {
216                    return regExpGroupMap.get(regexp);
217                }
218            }
219            return null;
220        }
221    
222        /**
223         * For the given group name, return the package list, on which it is mapped.
224         * Create a new list, if not found.
225         *
226         * @param map Map to be searched for gorup name.
227         * @param groupname Group name to search.
228         */
229        List<PackageDoc> getPkgList(Map<String,List<PackageDoc>> map, String groupname) {
230            List<PackageDoc> list = map.get(groupname);
231            if (list == null) {
232                list = new ArrayList<PackageDoc>();
233                map.put(groupname, list);
234            }
235            return list;
236        }
237    
238        /**
239         * Return the list of groups, in the same order as specified
240         * on the command line.
241         */
242        public List<String> getGroupList() {
243            return groupList;
244        }
245    }