head	1.19;
access;
symbols
	TIGRIS_1_1_0RC2:1.2
	TIGRIS_1_1_0RC1:1.2
	TIGRIS_1_1:1.2.0.4
	TIGRIS_1_0_8:1.2
	TIGRIS_1_0_8RC3:1.2
	TIGRIS_1_0_8RC2:1.2
	TIGRIS_1_0_8RC1:1.2
	TIGRIS_1_0_7:1.2
	TIGRIS_1_0_7RC3:1.2
	TIGRIS_1_0_7RC2:1.2
	TIGRIS_1_0_7RC1:1.2
	TIGRIS_1_0_6:1.2
	TIGRIS_1_0_6RC5:1.2
	TIGRIS_1_0_6RC4:1.2
	TIGRIS_1_0_6RC3:1.2
	TIGRIS_1_0_6RC2:1.2
	TIGRIS_1_0_6RC1:1.2
	TIGRIS_1_0_5:1.2
	TIGRIS_1_0_5RC6:1.2
	TIGRIS_1_0_5RC5:1.2
	TIGRIS_1_0_5RC4:1.2
	TIGRIS_1_0_5RC3:1.2
	TIGRIS_1_0_5RC2:1.2
	TIGRIS_1_0_5RC1:1.2
	TIGRIS_1_0_4:1.2
	TIGRIS_1_0_3:1.2
	TIGRIS_1_0_2:1.2
	TIGRIS_1_0_1:1.2
	TIGRIS_1_0:1.2.0.6
	TIGRIS_1_0_0:1.2
	TIGRIS_1_0_0_RC1:1.2.0.2;
locks; strict;
comment	@# @;


1.19
date	2001.08.29.18.15.44;	author leonardr;	state Exp;
branches;
next	1.18;

1.18
date	2001.08.20.20.38.55;	author leonardr;	state Exp;
branches;
next	1.17;

1.17
date	2001.08.11.09.08.03;	author dlr;	state Exp;
branches;
next	1.16;

1.16
date	2001.08.03.01.33.22;	author dlr;	state Exp;
branches;
next	1.15;

1.15
date	2001.06.29.00.57.49;	author leonardr;	state Exp;
branches;
next	1.14;

1.14
date	2001.06.16.00.22.58;	author leonardr;	state Exp;
branches;
next	1.13;

1.13
date	2001.06.15.21.23.35;	author leonardr;	state Exp;
branches;
next	1.12;

1.12
date	2001.06.15.20.23.08;	author leonardr;	state Exp;
branches;
next	1.11;

1.11
date	2001.06.15.17.11.19;	author leonardr;	state Exp;
branches;
next	1.10;

1.10
date	2001.06.14.21.15.21;	author leonardr;	state Exp;
branches;
next	1.9;

1.9
date	2001.06.09.00.40.57;	author leonardr;	state Exp;
branches;
next	1.8;

1.8
date	2001.06.08.21.52.09;	author leonardr;	state Exp;
branches;
next	1.7;

1.7
date	2001.06.06.22.34.41;	author leonardr;	state Exp;
branches;
next	1.6;

1.6
date	2001.05.30.20.15.25;	author leonardr;	state Exp;
branches;
next	1.5;

1.5
date	2001.05.30.01.11.12;	author leonardr;	state Exp;
branches;
next	1.4;

1.4
date	2001.05.23.00.35.00;	author leonardr;	state Exp;
branches;
next	1.3;

1.3
date	2001.04.20.21.19.40;	author leonardr;	state Exp;
branches;
next	1.2;

1.2
date	2001.03.07.01.53.22;	author lucas;	state Exp;
branches;
next	1.1;

1.1
date	2001.03.07.01.13.15;	author lucas;	state Exp;
branches;
next	;


desc
@@


1.19
log
@Added new method for getting the recursive subprojects of a project.
@
text
@/* ================================================================
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.om;

import java.util.*;
import org.apache.torque.util.Criteria;
import org.apache.turbine.util.Log;

import org.tigris.helm.tool.cache.RegisterableCache;
import org.tigris.helm.tool.cache.TableTrackingBufferCache;

/**
 * Extension of generated Peer BaseProjectProjectgroupPeer.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 */
public class ProjectProjectgroupPeer extends BaseProjectProjectgroupPeer
{

    /**
     * Cache of project-project group mappings.
     */
    private static ProjectProjectgroupCache cache;

    static
    {
        try
        {
            cache = new ProjectProjectgroupCache();
        }
        catch (Exception e)
        {
            throw new RuntimeException("Unable to create user cache: " +
                                       e.getMessage());
        }
    }

    /**
     * Overridden BasePeer method to do a delete and then to send out
     * an appropriate CacheInvalidationEvent.
     */
    public static void doDelete(Criteria c)
        throws Exception
    {
        BaseProjectProjectgroupPeer.doDelete(c);
        uncache(ProjectProjectgroup.class);
    }        

    /**
     * Returns those projects which have the given project as a direct 
     * child.
     */
    public static List getDirectParents(Project child)
        throws Exception
    {
        List parents = (List)cache.directParents.get(child);
        if (parents == null)
        {
            Criteria c = new Criteria(2)
                .addJoin(ProjectProjectgroupPeer.PROJECT_GROUP_ID, 
                         ProjectPeer.PROJECT_ID)
                .add(ProjectProjectgroupPeer.PROJECT_ID, child.getID());
            parents = ProjectPeer.doSelect(c);
            cache.directParents.put(child, parents);
        }
        return parents;
    }

    /**
     * Returns those projects which have the given project as a direct
     * parent.  
     */
    public static List getDirectChildren(Project parent)
        throws Exception
    {
        List children = (List)cache.directChildren.get(parent);
        if (children == null)
        {
            children = ProjectPeer.doSelect(directChildrenCriteria(parent));
            cache.directChildren.put(parent, children);
        }
        return children;
    }

    /**
     * Returns those projects roles in which will recurse into the given
     * project.
     */
    public static List getPermissionedParents (Project child, 
                                               boolean findPublic)
        throws Exception
    {
        List parents = 
            (List)cache.permissionedParentMap(findPublic).get(child);
        if (parents == null)
        {
            cache.addPermissionedParents(child);
            parents = 
                (List)cache.permissionedParentMap(findPublic).get(child);
        }                
        return parents;
    }

    /**
     * Returns those projects which a role in the given project could
     * recurse to.
     */
    public static List getPermissionedChildren (Project parent)
        throws Exception
    {
        List children = (List)cache.permissionedChildren.get(parent);
        if (children == null)
        {
            cache.addPermissionedChildren(parent);
            children = (List)cache.permissionedChildren.get(parent);
        }                
        return children;
    }

    /**
     * Returns all projects which are subprojects of the given
     * project, recursively.
     */
    public static List getRecursiveSubprojects (Project parent)
        throws Exception
    {
        List children = (List)cache.recursiveSubprojects.get(parent);
        if (children == null)
        {
            cache.addRecursiveSubprojects(parent);
            children = (List)cache.recursiveSubprojects.get(parent);
        }                
        return children;
    }

    /**
     * Returns a criteria which will give all the direct children of the
     * given project.
     */
    protected static Criteria directChildrenCriteria(Project parent)
    {
        return new Criteria(2)
            .addJoin(ProjectProjectgroupPeer.PROJECT_ID, 
                     ProjectPeer.PROJECT_ID)
            .add(ProjectProjectgroupPeer.PROJECT_GROUP_ID, parent.getID());
    }

    /**
     * Cache of permissioned parent-child project mappings.
     */
    static class ProjectProjectgroupCache extends RegisterableCache
    {
        /**
         * Registered name of this cache.
         */
        private static final String NAME 
            = "Parent-child project mapping cache";

        /**
         * Minimum size of a cache.
         */
        private static final int MINIMUM_CACHE_SIZE = 50;

        /**
         * Cache of direct parents keyed by child.
         */
        TableTrackingBufferCache directParents;

        /**
         * Cache of direct children keyed by parent.
         */
        TableTrackingBufferCache directChildren;

        /**
         * Cache of recursive subprojects keyed by project.
         */
        TableTrackingBufferCache recursiveSubprojects;

        /**
         * Cache of permissioned children keyed by parent.
         */
        TableTrackingBufferCache permissionedChildren;

        /**
         * Cache of permissioned parents keyed by children. Includes
         * only parents who can reach the child without going through
         * a private project.  
         */
        TableTrackingBufferCache publicPermissionedParents;

        /**
         * Cache of permissioned parents keyed by child. Includes only
         * parents who cannot reachable the child except by passing
         * through at least one private project.  
         */
        TableTrackingBufferCache privatePermissionedParents;

        /**
         * The classes found interesting by the project-project group mapping
         * cache.
         */
        private static Class[] interestingClasses =
        {
            Project.class, 
            ProjectProjectgroup.class
        };

        /**
         * Registers with the {@@link org.tigris.helm.tool.cache.CacheRegistry}.
         */
        public ProjectProjectgroupCache()
            throws Exception
        {
            super(NAME, interestingClasses);
            directParents = makeCache();
            directChildren = makeCache();
            recursiveSubprojects = makeCache();
            permissionedChildren = makeCache();
            publicPermissionedParents = makeCache();
            privatePermissionedParents = makeCache();
        }

        /**
         * Creates a TableTrackingBufferCache for this cache to use.
         */
        private TableTrackingBufferCache makeCache()
            throws Exception
        {
            return new TableTrackingBufferCache
                (TABLE_NAME, Attribute.FRACTIONAL_PROJECT_CACHE_SIZE,
                 MINIMUM_CACHE_SIZE);
        }

        /**
         * Returns the map to be used.
         */
        public Map permissionedParentMap(boolean returnPublic)
        {
            return (returnPublic ? publicPermissionedParents : 
                    privatePermissionedParents);
        }

        /**
         * Adds this project's permissioned parents to its map.
         */
        private void addPermissionedParents(Project child)
            throws Exception
        {
            List publicParents = new ArrayList();
            List privateParents = new ArrayList();

            //Roles from the child recurse into itself, of course.
            publicParents.add(child);
           
            List directParents = child.getDirectParents();
            for (Iterator i = directParents.iterator(); i.hasNext();)
            {                
                Project parent = (Project)i.next();
                if (!(parent.isCategory() && !child.isCategory()))
                {
                    //This is a valid permissioned parent.
                    List parentsPublicParents = 
                        parent.getPermissionedParents(true);
                    List parentsPrivateParents = 
                        parent.getPermissionedParents(false);
                    addNonduplicates(privateParents, parentsPrivateParents);

                    List toAddTo = null;
                    if (child.isPrivate())
                    {
                        toAddTo = privateParents;
                    }
                    else
                    {
                        toAddTo = publicParents;
                    }

                    addNonduplicates(toAddTo, parentsPublicParents);
                }
            }

            publicPermissionedParents.put(child, publicParents);
            privatePermissionedParents.put(child, privateParents);
        }

        /**
         * Adds this project's recursive subprojects to its map.
         */
        private void addRecursiveSubprojects(Project parent)
            throws Exception
        {
            List children = new ArrayList();
            List directChildren = parent.getDirectChildren();
            String parentType = parent.getType();
            for (Iterator i = directChildren.iterator(); i.hasNext();)
            {
                Project child = (Project)i.next();
                if (child.potentialChildOf(parentType))
                {
                    //This is a valid recursive child.
                    children.add(child);
                    addNonduplicates
                        (children, getRecursiveSubprojects(child));
                }
            }
            recursiveSubprojects.put(parent, children);
        }

        /**
         * Adds this project's permissioned children to its map.
         */
        private void addPermissionedChildren(Project parent)
            throws Exception
        {
            List children = new ArrayList();

            //Roles from the parent recurse into itself, of course.
            children.add(parent);

            List directChildren = parent.getDirectChildren();
            for (Iterator i = directChildren.iterator(); i.hasNext();)
            {
                Project child = (Project)i.next();
                if (!(parent.isCategory() && !child.isCategory()))
                {
                    //This is a valid permissioned child.
                    addNonduplicates(children, 
                                     child.getPermissionedChildren());
                }
            }
            permissionedChildren.put(parent, children);
        }

        /**
         * Returns a string describing the current contents of the cache.
         */
        public String describeContents()
        {
            StringBuffer buf = new StringBuffer();           
            buf.append(permissionedChildren.describeParameters());

            buf.append("\n Direct parents of children: \n");
            appendProjectMapDescription(buf, directParents);

            buf.append("\n Direct children of projects: \n");
            appendProjectMapDescription(buf, directChildren);

            buf.append("\n Recursive subprojects of projects: \n");
            appendProjectMapDescription(buf, recursiveSubprojects);

            buf.append("\n Recursive permissioned children of parent projects: \n");
            appendProjectMapDescription(buf, permissionedChildren);
            
            buf.append("\n Recursive permissioned parents of child projects ")
                .append("(public path): \n");
            appendProjectMapDescription(buf, publicPermissionedParents);

            buf.append("\n Recursive permissioned parents of child projects ")
                .append("(no public path): \n");
            appendProjectMapDescription(buf, privatePermissionedParents);
            return buf.toString();
        }

        /**
         * Invalidates the cache.
         */
        public void invalidate()
            throws Exception
        {
            if (DEBUG)
            {
                Log.debug("About to invalidate " + NAME + ":");
                Log.debug(describeContents());
            }                   
            super.invalidate();
            directParents.invalidate();
            directChildren.invalidate();
            recursiveSubprojects.invalidate();
            permissionedChildren.invalidate();
            privatePermissionedParents.invalidate();
            publicPermissionedParents.invalidate();
        } 

        /**
         * Appends a description of the provided parent-child project
         * map to the provided StringBuffer.
         */
        private void appendProjectMapDescription(StringBuffer buf, 
                                                 Map projectMap)
        {
            for (Iterator i = projectMap.keySet().iterator(); i.hasNext();)
            {
                Project p = (Project)i.next();
                buf.append("  ").append(p.getName()).append(": ");
                List l = (List)projectMap.get(p);
                for (Iterator j = l.iterator(); j.hasNext();)
                {
                    Project q = (Project)j.next();
                    buf.append(q.getName()).append(' ');
                }
                buf.append('\n');
            }                
        }

        /**
         * Adds one list to another, ensuring that no duplicate
         * entries are added.
         */
        private void addNonduplicates(List l1, List l2)
        {
            for (Iterator i = l2.iterator(); i.hasNext();)
            {
                Object a = i.next();
                if (!l1.contains(a))
                {
                    l1.add(a);
                }
            }
        }
    }
}
@


1.18
log
@Prevented cluttering of mappings with duplicate entries. Also made the
cache interested in om.Project, to fix a bug where changing a
project's public/private status didn't invalidate the permission
caches.
@
text
@d165 16
d219 6
a224 1
         * Cache of permissioned parents keyed by children.
d261 1
d331 23
d392 3
d422 1
d450 2
a451 1
         * Add 
@


1.17
log
@Upgrading to Turbine 3.0-dev, Fulcrum 1.0, and Torque 1.0.
@
text
@d222 10
d237 1
a237 1
            super(NAME, ProjectProjectgroup.class);
d288 1
a288 1
                    privateParents.addAll(parentsPrivateParents);
d300 1
a300 1
                    toAddTo.addAll(parentsPublicParents);
d326 2
a327 1
                    children.addAll(child.getPermissionedChildren());
d400 14
@


1.16
log
@Print out all caches as part of self-description.
@
text
@d50 1
a50 1
import org.apache.turbine.util.db.Criteria;
@


1.15
log
@Implemented basic project group and category nesting.
@
text
@d330 6
@


1.14
log
@TTBC-bearing RegisterableCaches now describe the behavior of their TTBCs.
@
text
@d123 1
a123 5
            Criteria c = new Criteria(2)
                .addJoin(ProjectProjectgroupPeer.PROJECT_ID, 
                         ProjectPeer.PROJECT_ID)
                .add(ProjectProjectgroupPeer.PROJECT_GROUP_ID, parent.getID());
            children = ProjectPeer.doSelect(c);
d162 12
@


1.13
log
@TableTrackingBufferCache now watches the attribute containing its fractional size, rather than the fractional size itself.
@
text
@d320 1
@


1.12
log
@Rearranged attribute constants and added generic host attribute-editing interface to AdminUtils.
@
text
@a184 6
         * Size of a cache as a percentage of the size of the table
         * it's tracking.  
         */
        private double fractionalCacheSize;

        /**
a219 3
            fractionalCacheSize = Double.parseDouble
                (AttributePeer.getHostAttribute
                 (Attribute.FRACTIONAL_PROJECT_CACHE_SIZE));
d234 1
a234 1
                (TABLE_NAME, fractionalCacheSize,
a235 14
        }

        /**
         * Sets the fractional size of the caches.
         */
        public void setFractionalSize(double newSize)
            throws Exception
        {
            fractionalCacheSize = newSize;
            directParents.setFractionalSize(fractionalCacheSize);
            directChildren.setFractionalSize(fractionalCacheSize);
            permissionedChildren.setFractionalSize(fractionalCacheSize);
            publicPermissionedParents.setFractionalSize(fractionalCacheSize);
            privatePermissionedParents.setFractionalSize(fractionalCacheSize);
@


1.11
log
@Added code to handle the default fractional size of TableTrackingBufferCaches as host attributes.
@
text
@d228 1
a228 1
                 (AttributePeer.FRACTIONAL_PROJECT_CACHE_SIZE));
@


1.10
log
@Put TableTrackingBufferCache into practice; hopefully I can soon replace it with something smarter.
@
text
@d188 1
a188 1
        private static final double FRACTIONAL_CACHE_SIZE = .3;
d226 33
a258 14
            TableTrackingBufferCache[] caches = 
            {
                directParents,
                directChildren,
                permissionedChildren,
                publicPermissionedParents,
                privatePermissionedParents
            };
            for (int i = 0; i < caches.length; i++)
            {
                caches[i] = new TableTrackingBufferCache
                    (TABLE_NAME, FRACTIONAL_CACHE_SIZE,
                     MINIMUM_CACHE_SIZE);
            }
@


1.9
log
@super.invalidate() can throw Exception.
@
text
@a51 1
import org.apache.turbine.util.BufferCache;
d54 1
d67 14
a80 2
    private static ProjectProjectgroupCache cache
        = new ProjectProjectgroupCache();
d180 1
a180 1
         * Size of the project caches.
d182 7
a188 1
        private static final int SIZE_OF_MAPPING_CACHE = 50;
d193 1
a193 1
        Map directParents = new BufferCache(SIZE_OF_MAPPING_CACHE);
d198 1
a198 1
        Map directChildren = new BufferCache(SIZE_OF_MAPPING_CACHE);
d203 1
a203 1
        Map permissionedChildren = new BufferCache(SIZE_OF_MAPPING_CACHE);
d210 1
a210 1
        Map publicPermissionedParents = new BufferCache(SIZE_OF_MAPPING_CACHE);
d217 1
a217 2
        Map privatePermissionedParents = new BufferCache
            (SIZE_OF_MAPPING_CACHE);
d223 1
d226 14
d241 1
a241 1
        
d350 5
a354 3
            permissionedChildren.clear();
            privatePermissionedParents.clear();
            publicPermissionedParents.clear();
@


1.8
log
@Added ability for caches to keep track of number of invalidations.
@
text
@d310 1
@


1.7
log
@Public path is lost when the child is private, not when the parent is private.
@
text
@d313 1
a313 1
                Log.debug("Invalidating " + NAME + ":");
d316 1
a316 1

@


1.6
log
@Big cleanup of cache self-descriptions. Also added an admin tool to do manual selective cache invalidation.
@
text
@d245 1
a245 1
                    if (parent.isPrivate())
@


1.5
log
@Got rid of denormalized project-project group mapping table in favor of per-JVM caching solution.
@
text
@d82 40
d173 10
d185 1
a185 1
        Map permissionedChildren;
d192 1
a192 1
        Map publicPermissionedParents;
d199 2
a200 1
        Map privatePermissionedParents;
a207 6
            permissionedChildren = 
                new BufferCache(SIZE_OF_MAPPING_CACHE);
            publicPermissionedParents = 
                new BufferCache(SIZE_OF_MAPPING_CACHE);
            privatePermissionedParents = 
                new BufferCache(SIZE_OF_MAPPING_CACHE);
d293 1
a293 1
            buf.append("Recursive permissioned children of parent projects: \n");
d296 1
a296 1
            buf.append("Recursive permissioned parents of child projects ")
d300 1
a300 1
            buf.append("Recursive permissioned parents of child projects ")
d337 1
a337 1
                    buf.append(q.getName()).append(" ");
d339 1
a339 1
                buf.append("\n");
a340 1
            buf.append("\n");
@


1.4
log
@Cleanup of caching and cache invalidation.
@
text
@d51 4
a55 1
import org.tigris.helm.om.ProjectProjectgroup;
d63 7
d79 220
@


1.3
log
@Send CacheInvalidationEvents on deletes, and on saves of objects which are not themselves cached but which invalidate other caches when they are changed.
@
text
@d68 1
a68 1
        uncache(ProjectProjectgroupPeer.class);
@


1.2
log
@Corrected regex madness for Torque upgrade.  -dlr
@
text
@a59 18

    private static Map projectsByGroup = new HashMap();
    private static Map groupsByProject = new HashMap();

    public static List getProjectsForGroup(String groupID)
    {
        return getListFromMap(projectsByGroup, groupID);
    }

    public static List getGroupsForProject(String projectID)
    {
        return getListFromMap(groupsByProject, projectID);
    }

    //
    // Methods for dealing with the static maps.
    //

d61 2
a62 4
     * Clears and populates the static mapping of projects to groups and
     * groups to projects.
     *
     * @@exception Exception
d64 1
a64 1
    public static void rebuildStatic()
a65 49
    {        
        projectsByGroup.clear();
        groupsByProject.clear();

        List dbMappings = doSelect(new Criteria());
        for (Iterator i = dbMappings.iterator(); i.hasNext();)
        {
            ProjectProjectgroup ppg = (ProjectProjectgroup)i.next();
            String projectID = ppg.getProjectID();
            String groupID = ppg.getProjectGroupID();
            addToMapList(projectsByGroup, projectID, groupID);
            addToMapList(groupsByProject, projectID, groupID);
        }        
    }

    /**
     * Logs the mapping of projects to groups.
     */
    public static String dumpStatic()
    {
        StringBuffer s = new StringBuffer();
        s.append("Dump of project-projectgroup mappings:\n");
        for (Iterator i = groupsByProject.keySet().iterator(); i.hasNext();)
        {
            String projectID = (String)i.next();
            s.append(" Project " + projectID + " belongs to these groups: ");
            List groupsForProject = getGroupsForProject(projectID);
            for (Iterator j = groupsForProject.iterator(); i.hasNext();)
            {
                s.append((String)j.next()).append(' ');
            }
            s.append("\n");
        }
        return s.toString();
    }

    private static List getListFromMap(Map m, String key)
    {
        List l = null;
        if (m.containsKey(key))
        { 
            l = (List)m.get(key);
        } else {
            l = new ArrayList();
        }
        return l;
    }
    
    private static void addToMapList(Map m, String key, String value)
d67 2
a68 3
        List l = getListFromMap(m, key);
        l.add(value);
        m.put(key, l);
a69 2


@


1.1
log
@Moved in from peer package to handle Torque changes. -dlr
@
text
@a52 1

@

