nicolaken    2002/09/02 06:48:44

  Added:       jenny    build.xml
               jenny/src/java/org/apache/alexandria/jenny Jenny.java
                        Module.java Project.java Repository.java
                        Server.java Workspace.java
  Log:
  Initial commit of the Jenny sub-project.
  Code taken from Gump, and added package.
  this makes it possible to keep the Gump proposal running till the new
  version in alexandria works.
  Will just change module->project and project->subproject;
  Will make a Gump subproject also that depends on this on
  In the meantime use the Gump proposal.
  
  Revision  Changes    Path
  1.1                  jakarta-alexandria/jenny/build.xml
  
  Index: build.xml
  ===================================================================
  <project name="jenny" default="package" basedir=".">
  
    <target name="compile">
      <mkdir dir="build/classes"/>
      <javac srcdir="src/java" destdir="build/classes"/>
    </target>
  
    <target name="package" depends="compile">
      <jar destfile="build/jenny.jar" basedir="build/classes"/>
    </target>
    
    <!-- Merge the xml sources, resolving references -->
    <target name="jenny" depends="package">
      <java classname="org.apache.alexandria.jenny.Jenny" fork="yes" 
failonerror="true">
        <arg value="${workspace}"/>
      </java>
    </target>
   
    <!-- Generate javadocs -->
    <target name="javadocs" depends="package">
      <mkdir dir="build/javadocs"/>
      <javadoc packagenames="org.apache.alexandria.jenny.*" sourcepath="src/java" 
destdir="build/javadocs" classpath="build/jenny.jar">
      
      </javadoc>
    </target>
  
    <!-- Do everything -->
    <target name="all" depends="package,javadocs"/>
  
    <!-- Remove all outputs and intermediate results -->
    <target name="clean">
      <delete dir="build"/>
    </target>
    
  </project>
  
  
  
  1.1                  
jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Jenny.java
  
  Index: Jenny.java
  ===================================================================
  /*
   * $Header: 
/home/cvs/jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Jenny.java,v 
1.1 2002/09/02 13:48:43 nicolaken Exp $
   * $Revision: 1.1 $
   * $Date: 2002/09/02 13:48:43 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  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 the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Alexandria", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * 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 THE APACHE SOFTWARE FOUNDATION 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 the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
  
  package org.apache.alexandria.jenny;
   
  // TRaX classes
  import javax.xml.transform.dom.DOMSource;
  import javax.xml.transform.stream.StreamResult;
  import javax.xml.transform.TransformerFactory;
  
  // Java API for XML Parsing classes
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  
  // DOM classes
  import org.w3c.dom.Attr;
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.NamedNodeMap;
  import org.w3c.dom.Node;
  import org.xml.sax.SAXParseException;
  
  // Java classes
  import java.io.File;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.Vector;
  
  public class Jenny {
  
      DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
      TransformerFactory tFactory = TransformerFactory.newInstance();
  
      /**
       * Parse an XML source file into an DOM.
       * @param source name of source file
       * @return Node
       */
      private Document parse(String source) throws Exception {
          try {
              DocumentBuilder dBuilder = dFactory.newDocumentBuilder();
              return dBuilder.parse(source);
          } catch (SAXParseException e) {
              System.err.print("Error parsing file " + source);
              System.err.println(" line " + e.getLineNumber() + ": ");
              throw e;
          } catch (java.net.UnknownHostException uhe) {
              System.out.println(uhe.toString());
              return null;
          }
      }
      
      /**
       * Replace all occurances of @@DATE@@ with the current datestamp in
       * attribute values.  The datestamp to be used is persisted on disk,
       * so it will remain stable until the .timestamp file is touched or
       * removed.
       * @param parent node to be processed recursively
       * @param dstamp string to substitute for @@DATE@@
       */
      private void replaceDate(Element parent, String dstamp) {
         Node first = parent.getFirstChild();
         for (Node child=first; child != null; child=child.getNextSibling()) {
             if (child.getNodeType()==Node.ELEMENT_NODE) {
                 NamedNodeMap attrs = ((Element)child).getAttributes();
                 for (int i=0; i<attrs.getLength(); i++) {
                     Node a = attrs.item(i);
                     String v = a.getNodeValue();
                     int start = v.indexOf("@@DATE@@");
                     while (start>=0) {
                         v = v.substring(0,start) + dstamp + v.substring(start+8);
                         ((Element)child).setAttribute(a.getNodeName(),v);
                         start = v.indexOf("@@DATE@@");
                     }
                 }
                 replaceDate((Element)child, dstamp);
             }
         }
      }
  
      /**
       * Move child nodes (attributes, elements, text, etc).
       * @param source element to move from
       * @param target element to update
       */
      protected static void moveChildren(Element source, Element target) {
         Node child=source.getFirstChild();
         while (child != null) {
             Node next=child.getNextSibling();
             target.appendChild(child);
             child=next;
         }
  
         NamedNodeMap attrs = source.getAttributes();
         for (int i=0; i<attrs.getLength(); i++) {
             Node a = attrs.item(i);
             if (target.getAttributeNode(a.getNodeName())==null) {
                 target.setAttribute(a.getNodeName(),a.getNodeValue());
             }
         }
      }
  
      /**
       * Expand hrefs in place, recursively.
       * @param node source element
       */
      private Element expand(Element node) throws Exception {
         // expand hrefs
         Attr href = node.getAttributeNode("href");
         if (href != null && !node.getNodeName().equals("url")) {
             String source=href.getValue();
             Node sub = parse(source);
  
             boolean isExtern = source.startsWith("http://";)
                         || source.startsWith("file://");
  
             if (sub != null) {
                 if (source.lastIndexOf(".")>0) {
                     source=source.substring(0,source.lastIndexOf("."));
                 }
  
                 if (source.lastIndexOf("/")>0) {
                     source=source.substring(source.lastIndexOf("/")+1);
                 }
  
                 node.removeAttribute("href");
                 node.setAttribute("defined-in", source);
  
                 Document doc = node.getOwnerDocument();
                 Element copy = null;
                 Node first = sub.getFirstChild();
                 for (Node child=first; child!=null; 
                      child=child.getNextSibling()) {
                     if (child.getNodeType() == Node.ELEMENT_NODE) {
                         copy = (Element)doc.importNode(child, true);
                         break;
                     }
                 }
  
                 moveChildren(node, copy);
  
                 node.getParentNode().replaceChild(copy,node);
                 node = copy;
  
                 // if expanded xml is not local to gump, make a local copy
                 // and "redirect" location using extern-prefix attribute
                 if (isExtern) {
                     String prefix = node.getNodeName() + "_";
                     // try to use name attribute as source, otherwise cross
                     // fingers and hope the current source is unique
                     if (node.getAttribute("name") != null) {
                         source = node.getAttribute("name");
                     }
                     output (sub, "work/" + prefix + source + ".xml");
                     node.setAttribute("defined-in", source);
                     node.setAttribute("extern-prefix",prefix);
                 }
             }
         }
  
         // move all profile information to the front
         Node first = node.getFirstChild();
         for (Node child=first; child!=null; child=child.getNextSibling()) {
             if (child.getNodeName().equals("profile")) {
                child = expand((Element)child);
                while (child.getFirstChild() != null) {
                   node.insertBefore(child.getFirstChild(), first); 
                }
             }
         }
  
         // recurse through the children
         first = node.getFirstChild();
         for (Node child=first; child != null; child=child.getNextSibling()) {
             if (child.getNodeType()==Node.ELEMENT_NODE) {
                 Element elem = (Element)child;
                 try {
                     child=expand(elem);
                 } catch (Throwable t) {
                     String tagName = elem.getTagName();
                     String nameAttr = elem.getAttribute("name");
                     String hrefAttr = elem.getAttribute("href");
  
                     StringBuffer name = new StringBuffer(tagName);
                     if (!"".equals(nameAttr)) {
                         name.append(" with name ").append(nameAttr);
                     }
                     if (!"".equals(hrefAttr)) {
                         name.append(" with href ").append(hrefAttr);
                     }
                     
                     System.err.println("Failed to expand "
                                        + name.toString()
                                        + " because of exception "
                                        + t);
                 }
             }
         }
  
         return node;
      }
  
      /**
       * Merge the contents of nodes with the same value for the name attribute.
       * Attributes from later definitions get added (or overlay) prior
       * definitions.  Elements get appended.  Elements marked for removal are
       * removed.
       * @param type Element localname.  Typically project or repository.
       * @param document Starting point for search.
       * @return hashtable of resulting elements
       */
      private Hashtable merge(String type, Node document)
          throws Exception
      {
          boolean needRemove = false;
          Hashtable list = new Hashtable();
  
          Node child=document.getFirstChild();
          for (; child!=null; child=child.getNextSibling()) {
              if (!child.getNodeName().equals(type)) continue;
              Element element = (Element) child;
              String name = element.getAttribute("name");
  
              // check for "removed" elements
              if (!needRemove
                      && !element.getAttribute("remove").equals("")) {
                  needRemove = true;
              }
  
              Element priorDefinition = (Element)list.get(name);
              if (priorDefinition != null && priorDefinition != element) {
                  Element parent  = (Element)priorDefinition.getParentNode();
                  String definedIn = parent.getAttribute("defined-in");
                  if (!definedIn.equals("")) {
                      element.setAttribute("defined-in",definedIn);
                  }
  
                  moveChildren(priorDefinition, element);
                  parent.removeChild(priorDefinition);
              }
              list.put(name, element);
          }
  
          if (needRemove) {
              Vector removeThem = new Vector();
              Enumeration enum = list.elements();
              while (enum.hasMoreElements()) {
                  Element element = (Element)enum.nextElement();
                  if (!element.getAttribute("remove").equals("")) {
                      removeThem.addElement(element);
                  }
              }
  
              enum = removeThem.elements();
              while (enum.hasMoreElements()) {
                  Element element = (Element)enum.nextElement();
                  element.getParentNode().removeChild(element);
                  list.remove(element.getAttribute("name"));
              }
          }
  
          return list;
      }
  
      /**
       * Serialize a DOM tree to file.
       * @param dom document to be transformed
       * @param dest name of file where the results are to be placed
       */
      private void output(Node dom, String dest) throws Exception {
          DOMSource in = new DOMSource(dom);
          StreamResult out = new StreamResult(dest);
          tFactory.newTransformer().transform(in, out);
      }
  
      /**
       * merge, sort, and insert defaults into a workspace
       * @param source document to be transformed
       */
      private Jenny(String source) throws Exception {
  
          // Obtain the date to be used
          Date lastModified = new Date();
          try {
              File timestamp = new File(".timestamp");
              timestamp.createNewFile();
              lastModified = new Date(timestamp.lastModified());
          } catch (java.io.IOException ioe) {
              ioe.printStackTrace();
          }
          String dstamp = (new SimpleDateFormat("yyyyMMdd")).format(lastModified);
  
          // process documents
          Document doc = parse(source);
          Element workspace = (Element)doc.getFirstChild();
          Workspace.init(workspace);
          expand(workspace);
          replaceDate(workspace, dstamp);
          Repository.load(merge("repository", workspace).elements());
          Server.load(merge("server", workspace).elements());
          Module.load(merge("module",workspace).elements());
          Project.load(merge("project",workspace).elements());
          output (doc, "work/merge.xml");
      }
  
      /**
       * Combine, colasce, and sort the files referenced by a workspace
       * definition into a single XML file.
       */
      public static void main(String[] args) {
          try {
              if (args.length == 1) {
                  new Jenny(args[0]);
              } else {
                  System.out.println("Usage: Jenny workspace.xml");
              }
          } catch (Exception e) {
              e.printStackTrace();
              System.exit(99);
          }
      }
  }
  
  
  
  1.1                  
jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Module.java
  
  Index: Module.java
  ===================================================================
  /*
   * $Header: 
/home/cvs/jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Module.java,v 
1.1 2002/09/02 13:48:43 nicolaken Exp $
   * $Revision: 1.1 $
   * $Date: 2002/09/02 13:48:43 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  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 the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Alexandria", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * 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 THE APACHE SOFTWARE FOUNDATION 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 the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
   
  package org.apache.alexandria.jenny;
   
  // DOM classes
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  
  // Java classes
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.FileReader;
  import java.io.FileWriter;
  import java.io.PrintWriter;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.Vector;
  
  public class Module {
  
      static private Hashtable modules = new Hashtable();
      private Element element;
      private Element cvs;
      private String name;
      private String srcdir;
      private Element description;
      private Element url;
  
      private static String cvspass = System.getProperty("user.home")+"/.cvspass";
  
      /** cvspass char conversion data */
      private static final char shifts[] = {
            0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
           16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
          114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
          111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
           41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
          125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
           36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
           58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
          225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
          199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
          174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
          207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
          192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
          227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
          182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
          243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152 };
  
      /**
       * Create a set of Module definitions based on XML nodes.
       * @param moudules list of &lt;module&gt; elements
       */
      public static void load(Enumeration modules) throws Exception {
          Vector dropThem = new Vector();
          while (modules.hasMoreElements()) {
              Element element = (Element)modules.nextElement();
              try {
                  new Module(element);
              } catch (Throwable t) {
                  dropThem.addElement(element);
                  System.err.println("Dropping module "
                                     + element.getAttribute("name")
                                     + " because of Exception " + t);
              }
          }
  
          Enumeration enum = dropThem.elements();
          while (enum.hasMoreElements()) {
              Element element = (Element)enum.nextElement();
              element.getParentNode().removeChild(element);
          }
  
          cvslogin();
      }
  
      /**
       * Find a Module given its name.
       * @param String module name
       * @return Module
       */
      public static Module find(String name) {
          return (Module) modules.get(name);
      }
  
      /**
       * Constructor.
       * @param element &lt;module&gt; element
       */
      public Module(Element element) throws Exception {
          this.element = element;
          name = element.getAttribute("name");
          
          computeSrcDir();
          promoteProjects();
          resolveCvsroot();
          
          Node child=element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (child.getNodeName().equals("description")) {
                  description = (Element) child;
              } else if (child.getNodeName().equals("url")) {
                  url = (Element) child;
              } else if (child.getNodeName().equals("svn")) {
                  Element svn = (Element) child;
                  svn.setAttribute("srcdir", name);
              }
          }
          
          modules.put(name, this);
      }
  
      /**
       * Property accessor for srcdir attribute.
       * @return Source directory associated with this module
       */
      public String getSrcDir() {
          return srcdir;
      }
  
      /**
       * Property accessor for description attribute.
       * @param name attribute name
       * @return Value of the specified attribute.
       */
      public Element getDescription() {
          return description;
      }
  
      /**
       * Property accessor for url attribute.
       * @param name attribute name
       * @return Value of the specified attribute.
       */
      public Element getUrl() {
          return url;
      }
  
      /**
       * Append arbitrary XML data to this node.
       * @param child Node to be deep copied to this tree.
       */
      public void appendChild(Node child) {
          element.appendChild(child.cloneNode(true));
      }
  
      /**
       * Resolve the source directory for this module.
       */
      private void computeSrcDir() throws Exception {
          srcdir=element.getAttribute("srcdir");
          if (srcdir.equals("")) srcdir=name;
          srcdir = Workspace.getBaseDir() + "/" + srcdir;
  
          element.setAttribute("srcdir", srcdir);
      }
  
      /**
       * Set module name on any projects contained herein, and move the
       * element up to the workspace.
       */
      private void promoteProjects() throws Exception {
          Vector projects = new Vector();
  
          // Collect a list of projects, marking them as we go
          Node child=element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (! child.getNodeName().equals("project")) continue;
  
              Element project = (Element) child;
              if (project.getAttributeNode("module") == null) {
                  project.setAttribute("module", name);
              }
  
              projects.add(project);
          }
  
          // Move each project from the module to the workspace
          Node parent = element.getParentNode();
          String definedIn = element.getAttribute("defined-in");
          String prefix = element.getAttribute("extern-prefix");
          for (Enumeration e=projects.elements(); e.hasMoreElements(); ) {
              Element project = (Element) e.nextElement();
              if (project.getAttributeNode("defined-in") == null) {
                 project.setAttribute("defined-in", definedIn);
                 if (!prefix.equals(""))
                     project.setAttribute("extern-prefix",prefix);
              }
              element.removeChild(project);
              parent.appendChild(project);
          }
      }
  
      /**
       * Resolves a repository name into a cvsroot.  If there are multiple
       * cvs entries, first they are combined into a single elemnt.  Then
       * the single resulting element (if any) will get decorated with the
       * the module name, cvsroot, and password info.
       */
      private void resolveCvsroot() throws Exception {
          Node child=element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (! child.getNodeName().equals("cvs")) continue;
              if (cvs == null) {
                  cvs = (Element) child;
              } else {
                  Jenny.moveChildren((Element) child, cvs);
              }
  
          }
  
          if (cvs != null) {
              cvs.setAttribute("srcdir", name);
              if (cvs.getAttributeNode("module") == null) {
                  cvs.setAttribute("module", name);
              }
  
              String tag = element.getAttribute("tag");
              if (!tag.equals("")) cvs.setAttribute("tag", tag);
  
              String value = cvs.getAttribute("repository");
              Repository r = Repository.find(value);
              if (r == null) {
                  throw new Exception(
                     "repository \"" + value + 
                     "\" not found processing module " + name);
              }
               
              String cvsroot = ":" + r.get("method") + ":";
  
              if (r.get("user").length() > 0) {
                  cvsroot += r.get("user");
              }
  
              if (r.get("hostname").length() > 0) {
  
                  cvsroot += "@";
                  if (cvs.getAttributeNode("host-prefix") != null) {
                      cvsroot += cvs.getAttribute("host-prefix") + ".";
                  }
                  cvsroot += r.get("hostname") + ":";
              }
  
              cvsroot += r.get("path");
              if (cvs.getAttributeNode("dir") != null) {
                  cvsroot += "/" + cvs.getAttribute("dir");
              }
  
              cvs.setAttribute("cvsroot", cvsroot);
              cvs.setAttribute("password", r.get("password"));
          }
      }
  
  
      /**
       * Add entries to cvspass, as necessary, to simulate logon to 
       * cvs repositories accessed as :pserver:.  Note: the .cvspass file
       * will not be updated unless there are new entries to add.
       */
      private static void cvslogin() throws Exception {
  
          Hashtable password = new Hashtable();
          File file = new File(cvspass);
  
          // read existing cvsroot entries
  
          if (file.exists()) {
              BufferedReader reader = new BufferedReader(new FileReader(file));
  
              for (String line = null; (line=reader.readLine())!=null; ) {
                  int split = line.indexOf(' ');
                  password.put(line.substring(0,split), line.substring(split+1));
              }
      
              reader.close();
          }
  
          // append new entries
  
          PrintWriter writer = null;
  
          for (Enumeration e=modules.elements(); e.hasMoreElements(); ) {
              Element cvs = ((Module) e.nextElement()).cvs;
              if (cvs == null) continue;
  
              String cvsroot = cvs.getAttribute("cvsroot");
              if (!cvsroot.startsWith(":pserver:")) continue;
  
              if (password.get(cvsroot) == null) {
                  if (writer == null) {
                      writer = new PrintWriter(new FileWriter(cvspass, true));
                  }
                  String data = "A" + mangle(cvs.getAttribute("password"));
                  password.put(cvsroot, data);
                  writer.println(cvsroot + " " + data);
              }
          }
  
          if (writer != null) writer.close();
      }
  
      /**
       * Encode a password using the same encryption scheme that cvs uses.
       */
      private static String mangle(String password){
          char[] buf = password.toCharArray();
          for(int i=0; i<buf.length; i++) {
              buf[i] = shifts[buf[i]];
          }
          return new String(buf);
      }
  
  }
  
  
  
  1.1                  
jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Project.java
  
  Index: Project.java
  ===================================================================
  /*
   * $Header: 
/home/cvs/jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Project.java,v 
1.1 2002/09/02 13:48:43 nicolaken Exp $
   * $Revision: 1.1 $
   * $Date: 2002/09/02 13:48:43 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  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 the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Alexandria", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * 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 THE APACHE SOFTWARE FOUNDATION 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 the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
   
  package org.apache.alexandria.jenny;
   
  // DOM classes
  import org.w3c.dom.Attr;
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  
  // Java classes
  import java.util.Comparator;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.Set;
  import java.util.StringTokenizer;
  import java.util.TreeSet;
  import java.util.Vector;
  
  public class Project {
  
      static private Hashtable projects = new Hashtable();
      private Document document;
      private Element element;
      private String name;
      private Element ant = null;
      private Hashtable dependsOn = new Hashtable();
      private Hashtable referencedBy = new Hashtable();
      private Hashtable jars = new Hashtable();
      private Element description;
      private Element url;
      private Vector deliver = new Vector();
  
      private static String nagTo = null;
      private static String nagPrefix = null;
  
      /**
       * Create a set of Project definitions based on XML nodes.
       * @param elements list of &lt;project&gt; elements
       */
      public static void load(Enumeration elements) throws Exception {
          Element element = null;
          while (elements.hasMoreElements()) {
              try {
                  element = (Element)elements.nextElement();
                  new Project(element);
              } catch (Throwable t) {
                  System.err.println("Dropping project "
                                     + element.getAttribute("name")
                                     + " because of Exception " + t);
              }
          }
              
          // Resolve all references so that the XML can be processed in
          // one pass.
          for (Enumeration e=((Hashtable) projects.clone()).elements(); 
e.hasMoreElements();) {
              Project p = ((Project)(e.nextElement()));
              try {
                  p.expandDepends();
                  p.resolveProperties();
              } catch (Throwable t) {
                  System.err.println("Dropping project " + p.get("name")
                                     + " because of Exception " + t);
                  p.remove();
              }
          }
          
          sort();
      }
  
      /**
       * General attribute accessor.
       * @param name attribute name
       * @return Value of the specified attribute.
       */
      public String get(String name) {
          return element.getAttribute(name);
      }
  
      /**
       * Constructor.
       * @param element &lt;project&gt; element
       */
      public Project(Element element) throws Exception {
          this.element = element;
          document = element.getOwnerDocument();
          name = element.getAttribute("name");
  
          Element home = null;
          Element javadoc = null;
  
          Node child=element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (child.getNodeName().equals("depend")) {
                  dependsOn.put(((Element)child).getAttribute("project"), child);
              } else if (child.getNodeName().equals("option")) {
                  dependsOn.put(((Element)child).getAttribute("project"), child);
              } else if (child.getNodeName().equals("ant")) {
                  if (ant != null) {
                      // multiple ant children?  Merge them!
                      Jenny.moveChildren(ant, (Element)child);
                      element.removeChild(ant);
                  }
                  ant = (Element)child;
              } else if (child.getNodeName().equals("home")) {
                  home = (Element)child;
              } else if (child.getNodeName().equals("description")) {
                  description = (Element)child;
              } else if (child.getNodeName().equals("url")) {
                  url = (Element)child;
              } else if (child.getNodeName().equals("javadoc")) {
                  javadoc = (Element)child;
              } else if (child.getNodeName().equals("jar")) {
                  jars.put(((Element)child).getAttribute("id"), child);
              } else if (child.getNodeName().equals("deliver")) {
                  deliver.add(child);
              } else if (child.getNodeName().equals("nag")) {
                  expandNag((Element) child);
              }
          }
  
          computeHome(home);
  
          if (ant != null) {
              genProperties(ant);
              genDepends(ant);
              if (!get("target").equals("")) {
                  ant.setAttribute("target", get("target"));
              }
          }
  
          resolveJavadoc(javadoc);
          handleDeliver();
  
          // if only one jar is found, make sure that it can be accessed without
          // specifying an id.
          if (jars.size() == 1) {
              jars.put("", jars.elements().nextElement());
          }
  
          projects.put(name, this);
      }
  
      /**
       * Replace ant "depend" elements with "property" elements.  This is
       * a convenience "syntatic sugar" that makes for simpler project
       * definitions.  Attribute "property" becomes name.  Attributes
       * reference="jarpath" and classpath="true" are added.
       * @param ant &lt;ant&gt; element to be processed
       */
      private void genProperties(Element ant) throws Exception {
  
          Node child=ant.getFirstChild();
          while (child != null) {
              Node next = child.getNextSibling();
               
              if (child.getNodeName().equals("depend")) {
                  // create a new element based on existing element
                  Element property = document.createElement("property");
                  property.setAttribute("reference", "jarpath");
                  property.setAttribute("classpath", "add");
                  Jenny.moveChildren((Element)child, property);
      
                  // change property attribute to name attribute
                  if (property.getAttributeNode("name")==null) {
                     Attr pname = property.getAttributeNode("property");
                     if (pname != null) {
                         property.setAttribute("name",pname.getValue());
                         property.removeAttributeNode(pname);
                     }
                  }
  
                  // replace existing element with new one
                  ant.replaceChild(property, child);
              }
  
              child = next;
          }
      }
  
      /**
       * Generate Depend elements from property references to projects.
       * @param ant &lt;ant&gt; element to be processed
       */
      private void genDepends(Element ant) throws Exception {
          Node child=ant.getFirstChild();
          for (;child!=null; child=child.getNextSibling()) {
              if (!child.getNodeName().equals("property")) continue;
              Element property = (Element) child;
  
              String dependency = property.getAttribute("project");
              if (dependency.equals("")) continue;
              if (dependency.equals(name)) continue;
              if (dependsOn.get(dependency) != null) continue;
  
              if (property.getAttribute("reference").equals("srcdir")) continue;
  
              Element depend = document.createElement("depend");
              depend.setAttribute("project", dependency);
              if (property.getAttributeNode("classpath") == null) {
                  depend.appendChild(document.createElement("noclasspath"));
              }
  
              Attr runtime = property.getAttributeNode("runtime");
              if (runtime != null) {
                  depend.setAttribute("runtime", runtime.getValue());
              }
  
              element.appendChild(depend);
              dependsOn.put(dependency, depend);
          }
      }
  
      /**
       * Resolve home directory.  In the process copy any description and
       * url elements necessary to complete this definition.
       * @param home &lt;ant&gt; element which may contain info
       */
      private void computeHome(Element home) {
          String basedir = Workspace.getBaseDir();
  
          Module module = Module.find(element.getAttribute("module"));
          if (module == null) return;
  
          String srcdir;
          String pkg = element.getAttribute("package");
          if (pkg.equals("")) {
              srcdir = module.getSrcDir();
          } else { 
              srcdir = Workspace.getPkgDir() + "/" + pkg;
          }
          element.setAttribute("srcdir", srcdir);
  
          // if a description is not provided, copy the one from the module
          if (description == null) {
              description = module.getDescription();
              if (description != null) {
                  element.appendChild(description.cloneNode(true));
              }
          }
  
          // if a url is not provided, copy the one from the module
          if (url == null) {
              url = module.getUrl();
              if (url != null) {
                  element.appendChild(url.cloneNode(true));
              }
          }
  
          // compute home directory
          String result=element.getAttribute("home");
          if (result.equals("")) {
              if (home != null) {
                  String attr;
                  if (! (attr=home.getAttribute("parent")).equals("")) {
                      result = basedir + "/" + attr;
                  } else if (! (attr=home.getAttribute("nested")).equals("")) {
                      result = srcdir + "/" + attr;
                  } else if (! (attr=home.getAttribute("dir")).equals("")) {
                      result = attr;
                  }
              }
  
              if (result.equals("")) result=srcdir;
              element.setAttribute("home", result);
          } else {
              element.setAttribute("srcdir", result);
          }
      }
  
      /**
       * Copy selected attribute and elements from dependent projects.  This
       * simplifies generation by essentially making project definitions
       * self contained.
       */
      private void expandDepends() throws Exception {
          String jardir = Workspace.getJarDir();
  
          for (Enumeration e=dependsOn.keys(); e.hasMoreElements(); ) {
              String name = (String)e.nextElement();
              Element depend = (Element) dependsOn.get(name);
              Project target = (Project)projects.get(name);
  
              String idsAttr = depend.getAttribute("ids");
              StringTokenizer tok = new StringTokenizer(idsAttr, " ", false);
              Vector jarIds = new Vector();
              while (tok.hasMoreTokens()) {
                  jarIds.add(tok.nextToken());
              }
              
              boolean buildable = false;
  
              if (!depend.getNodeName().equals("option")) {
                  require(target, "project", name);
              }
              if (target == null) continue;
  
              target.referencedBy.put(this, depend);
              depend.setAttribute("home",target.get("home"));
              depend.setAttribute("defined-in",target.get("defined-in"));
  
              Node child=target.element.getFirstChild();
              for (; child != null; child=child.getNextSibling()) {
                  if (child.getNodeName().equals("jar")) {
                      String id = ((Element) child).getAttribute("id");
                      if (idsAttr.equals("") 
                          || (!id.equals("") && jarIds.contains(id))) {
                          depend.appendChild(child.cloneNode(false));
                      }
                  } else if (child.getNodeName().equals("ant")) {
                      depend.appendChild(document.createElement("ant"));
                      buildable = true;
                  } else if (child.getNodeName().equals("script")) {
                      depend.appendChild(document.createElement("script"));
                      buildable = true;
                  }
              }
  
              if (buildable && !jardir.equals("")) {
                  String module = target.get("module");
                  depend.setAttribute("home", jardir + "/" + module);
                  child=depend.getFirstChild();
                  for (; child != null; child=child.getNextSibling()) {
                    if (child.getNodeName().equals("jar")) {
                        String jarname = ((Element)child).getAttribute("name");
                        jarname = jarname.substring(jarname.lastIndexOf("/")+1);
                        ((Element)child).setAttribute("name", jarname);
                    }
                  }
              }
          }
      }
  
      /**
       * Determine if this project has any unsatisfied dependencies left
       * on the todo list.
       * @param todo the list of projects yet to be done (includes this one).
       * @return true if this project is ready to go
       */
      private boolean isReady(Set todo) {
          for (Enumeration e=dependsOn.keys(); e.hasMoreElements();) {
              if (todo.contains(e.nextElement())) return false;
          }
          return true;
      }
  
      /**
       * Determine if this project is referenced by anything on the todo list.
       * @param todo the list of projects yet to be done (includes this one).
       * @return true if this project is holding up progress
       */
      private boolean isPrereq(Set todo) {
          for (Enumeration e=referencedBy.keys(); e.hasMoreElements();) {
              Project p = (Project)e.nextElement();
              if (todo.contains(p.name)) return true;
          }
          return false;
      }
  
      /**
       * Move this element to the end of the parents set of children.
       */
      private void moveToLast() {
          Element parent = (Element) element.getParentNode();
          parent.removeChild(element);
          parent.appendChild(element);
      }
  
      /**
       * Process all inherited dependencies.  
       */
      private void inheritDependencies() {
          Vector inheritance = new Vector();
  
          for (Enumeration e=dependsOn.elements(); e.hasMoreElements(); ) {
              Element depend = (Element) e.nextElement();
              String inherit = depend.getAttribute("inherit");
              Project p = (Project) projects.get(depend.getAttribute("project"));
              if (p == null) continue;
  
              for (Enumeration d=p.dependsOn.keys(); d.hasMoreElements(); ) {
                  String name = (String) d.nextElement();
                  if (dependsOn.get(name) != null) continue;
                  Element source = (Element) p.dependsOn.get(name);
                  String type = source.getNodeName();
  
                  // if inherit="all" is specified on a depends element, then all
                  // of the optional and depends elements on the referenced 
                  // project are inherited by this project.
                  //
                  // if inherit="hard", the same thing happens except that all 
                  // optional dependencies are converted to "hard" dependencies
                  // in the copy.
                  if (inherit.equals("all") || inherit.equals("hard")) {
                      Element clone = (Element) source.cloneNode(true);
                      if (inherit.equals("hard") && type.equals("option")) {
                          Element renamed = document.createElement("depend");
                          Jenny.moveChildren(clone, renamed);
                          clone = renamed;
                      }
                      inheritance.add(clone);
  
                  // look for runtime="true" dependencies in the referenced
                  // project.  Convert depends to options if the reference to
                  // the project is an option.
                  } else if (inherit.equals("runtime")) {
                      if (source.getAttribute("runtime").equals("true")) {
                          Element clone = (Element) source.cloneNode(true);
                          if (type.equals("option")) {
                              Element renamed = document.createElement("option");
                              Jenny.moveChildren(clone, renamed);
                              clone = renamed;
                          }
                          inheritance.add(clone);
                      }
                  }
  
                  // if this project depends on any project which in turn has
                  // a dependency which specifies inherit="jars", then inherit
                  // that dependency.
                  if (source.getAttribute("inherit").equals("jars")) {
                      Element clone = (Element) source.cloneNode(true);
                      clone.setAttribute("runtime", "true");
                      inheritance.add(clone);
                  }
              }
          }
  
          // Add the inherited nodes
          for (Enumeration e=inheritance.elements(); e.hasMoreElements(); ) {
              Element inherited = (Element) e.nextElement();
              String project = inherited.getAttribute("project");
              if (dependsOn.get(project) == null) {
                  inherited.setAttribute("inherited", "true");
                  dependsOn.put(project, inherited);
                  element.appendChild(inherited);
              }
          }
      }
  
      /**
       * Implement a deterministing sort order.  Projects which are most
       * referenced get priority.  After that, order is determined alphabetically.
       */
      private static class Ranker implements Comparator {
          public int compare(Object o1, Object o2) {
              Project p1 = (Project) projects.get(o1);
              Project p2 = (Project) projects.get(o2);
              if (p1 == p2) return 0;
              if (p1 == null) return -1;
              if (p2 == null) return 1;
  
              int diff = p2.referencedBy.size() - p1.referencedBy.size();
              if (diff != 0) return diff;
  
              return ((String)o1).compareTo((String)o2);
          }
      }
  
      /**
       * Sort all projects in an order that respects the dependencies.  If such
       * an order can not be determined, print out the ones that form a loop.
       * As we determine the order, process the inherited dependencies.  This
       * way the inheritance will recurse properly.
       */
      private static void sort() throws Exception {
          TreeSet todo = new TreeSet(new Ranker());
          todo.addAll(projects.keySet());
  
          // As long as there are projects which are ready, put the next
          // available one at the end of the list.
          for (Iterator i=todo.iterator(); i.hasNext();) {
              Project p = (Project) projects.get(i.next());
              if (p.isReady(todo)) {
                  p.moveToLast();
                  p.inheritDependencies();
                  todo.remove(p.name);
                  i=todo.iterator();
              }
          }
              
          // Did we succeed?
          if (todo.isEmpty()) return;
  
          // Remove all elements which are not prereqed by any projects
          // remaining
          if (todo.isEmpty()) return;
          for (Iterator i=todo.iterator(); i.hasNext();) {
              Project p = (Project) projects.get(i.next());
              if (!p.isPrereq(todo)) {
                  todo.remove(p.name);
                  i=todo.iterator();
              }
          }
  
  
          // Construct a list of the rest, and throw an exception
          String message="Circular dependency loop involving:";
          for (Iterator i=todo.iterator(); i.hasNext();) {
              Project p = (Project) projects.get(i.next());
              message += "\n  " + p.name;
          }
          throw new Exception(message);
      }
  
      /**
       * Resolve property references, based on the value of "reference"
       * attribute (if present).  Supported values for reference are:
       *
       * <ul>
       * <li> home: the home directory for the referenced project </li>
       * <li> jar: the simple name (path relative to home) of the jar in a 
       * referenced project. </li>
       * <li> jarpath: the fully qualified path of the jar in a referenced 
       * project. </li>
       * <li> srcdir: the srcdir for the module containing the project. </li>
       * <li> path: a path which is to be interpreted relative to the srcdir 
       * for the module containing this project </li>
       * </ul>
       */
      private void resolveProperties() throws Exception {
          if (ant == null) return;
  
          Node child=ant.getFirstChild();
          for (;child!=null; child=child.getNextSibling()) {
              if (!child.getNodeName().equals("property")) continue;
              Element property = (Element) child;
              if (property.getAttributeNode("value") != null) continue;
  
              String reference = property.getAttribute("reference");
              String projectName = property.getAttribute("project");
  
              String value = null;
  
              if (reference.equals("home")) {
                  Project project = (Project) projects.get(projectName);
                  require (project, "project", projectName);
                  value = project.get("home");
                  property.setAttribute("type", "path");
              } else if (reference.equals("jar")) {
                  String id = property.getAttribute("id");
                  Element jar = getJar (projectName, property.getAttribute("id"));
                  value = jar.getAttribute("name"); 
              } else if (reference.equals("jarpath")) {
                  Project project = (Project) projects.get(projectName);
                  Element jar = getJar (projectName, property.getAttribute("id"));
                  value = project.get("home") + "/" + jar.getAttribute("name"); 
                  property.setAttribute("type", "path");
              } else if (reference.equals("srcdir")) {
                  Module module = Module.find(projectName);
                  require (module, "module", projectName);
                  value = module.getSrcDir();
                  property.setAttribute("type", "path");
              } else if (property.getAttributeNode("path") != null) {
                  Project project = (Project) projects.get(projectName);
                  if (project == null) project=this;
                  value = project.get("srcdir");
                  value += "/" + property.getAttribute("path");
                  property.setAttribute("type", "path");
              }
  
              if (value != null) property.setAttribute("value", value);
          }
      }
  
      /**
       * Locate a specific jar from a dependency, issuing an exception if 
       * the jar can not be found.
       * @param projectName the name of the project
       * @param id the identifier for which jar is desired
       * @return Value of the specified attribute.
       */
      private Element getJar(String projectName, String id) 
          throws Exception 
      {
          Project project = (Project) projects.get(projectName);
          require (project, "project", projectName);
  
          Element jar = (Element)project.jars.get(id);
          if (jar != null) return jar;
  
          if (!id.equals("")) {
  
              throw new Exception(
                 "A jar with id \"" + id + "\" was not found in project \"" + 
                 projectName + "\" referenced by project " + name);
  
          } else if (project.jars.size() > 1) {
  
              throw new Exception(
                 "Multiple jars defined by project \"" + projectName + "\" " + 
                 "referenced by project \"" + name + "\"; " +
                 "an id attribute is required to select the one you want.");
  
          } else {
  
              throw new Exception(
                 "Project \"" + projectName + "\" referenced by project " + 
                 name + " defines no jars as output.");
  
          }
  
      }
  
      /**
       * If a javadoc child was found, add any missing project, module,
       * or description information; resolve path; and copy the resulting
       * node to the specified module.
       * @param javadoc child XML element
       */
      private void resolveJavadoc(Element javadoc) throws Exception {
          if (javadoc == null) return;
  
          // retrieve url and dir of javadoc from the workspace
          Element config = Workspace.getJavaDoc();
          if (config == null) return;
          String url = config.getAttribute("url");
          String javadocDir = config.getAttribute("dir");
  
          // default project attribute to the name of this project
          if (javadoc.getAttributeNode("project") == null)
              javadoc.setAttribute("project", name);
  
          // default module attribute to the module which this project belongs
          String moduleName = javadoc.getAttribute("module");
          if (moduleName.equals("")) moduleName = this.get("module");
          Module module = Module.find(moduleName);
          require (module, "module", moduleName);
  
          if (!moduleName.equals(this.get("module"))) {
              javadoc.setAttribute("defined-in", this.get("module"));
          }
  
          // if there are no child nodes, add this project's description
          if (!javadoc.hasChildNodes() && description!=null) {
              Element desc = (Element) description.cloneNode(true);
              javadoc.appendChild(desc);
          }
  
          // resolve relative and full path to this javadoc entry
          String path = javadoc.getAttribute("nested");
          String fullpath;
          if (!path.equals("")) {
              fullpath = get("srcdir") + "/" + path;
          } else {
              path = javadoc.getAttribute("parent");
              fullpath = Workspace.getBaseDir() + "/" + path;
          }
          path = moduleName + "/" + path;
  
          // for each description entry, resolve source, url, and dest attrs.
          Node child=javadoc.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (child.getNodeName().equals("description")) {
                  Element desc = (Element) child;
                  String dir = desc.getAttribute("dir");
                  String append = "";
                  if (!dir.equals("")) append = "/" + dir;
  
                  desc.setAttribute("source", fullpath + append);
  
                  if (url.equals("")) {
                      desc.setAttribute("url", "file:///" + fullpath + append);
                  } else {
                      desc.setAttribute("url", url + path + append);
                  }
  
                  if (!javadocDir.equals("")) {
                      desc.setAttribute("dest", javadocDir + "/" + path + append);
                  }
              }
          }
  
          // copy the entire result to the desired module
          module.appendChild(javadoc);
      }
  
      /**
       * Require that a specified object not be null, otherwise produce an error
       * message indicating what attribute the problem occurred on.
       * @param object the object which must not be null
       * @param attr the attribute from which this value was derived
       * @param value the value of this attribute
       * @return Value of the specified attribute.
       */
      private void require(Object object, String attr, String value) 
          throws Exception 
      {
          if (object != null) return;
  
          throw new Exception(
             attr + " \"" + value + "\" not found processing project " + name
          );
      }
  
      /**
       * Remove this Project instance from the workspace.
       */
      private void remove() {
          projects.remove(name);
          element.getParentNode().removeChild(element);
      }
  
      /**
       * For all deliver elements, add them to the corresponding site
       * elements, if these are defined.
       */
      private void handleDeliver() {
          Enumeration elements = deliver.elements();
          String srcdir = element.getAttribute("srcdir");
          while (elements.hasMoreElements()) {
              Element deliver = (Element) elements.nextElement();
              String siteName = deliver.getAttribute("tosite");
              Element site = Server.getSite(siteName);
              if (site != null) {
                  String fromdir = deliver.getAttribute("fromdir");
                  deliver.setAttribute("fromdir", srcdir + "/" + fromdir);
                  site.appendChild(deliver);
              }
          }
      }
  
      /**
       * Add default values for subjects and regexp if none have been
       * specified.
       *
       * Push attributes to nested regexp elements
       */
      private void expandNag(Element nag) {
          if (nagPrefix == null) {
              Element workspaceNag = Workspace.getNag();
              if (workspaceNag == null) {
                  nagPrefix = "[GUMP]";
              } else {
                  if (!workspaceNag.getAttribute("prefix").equals("")) {
                      nagPrefix = workspaceNag.getAttribute("prefix");
                  } else {
                      nagPrefix = "[GUMP]";
                  }
                  if (!workspaceNag.getAttribute("to").equals("")) {
                      nagTo = workspaceNag.getAttribute("to");
                  }
              }
          }
          
  
          String subject = nagPrefix + " Build Failure - "+name;
          String to = nagTo == null ? nag.getAttribute("to") : nagTo;
          String from = nag.getAttribute("from");
  
          if (!nag.getAttribute("subject").equals("")) {
              subject = nagPrefix + " " + nag.getAttribute("subject");
          }
          
          if (!nag.hasChildNodes()) {
              Element regexp = nag.getOwnerDocument().createElement("regexp");
              regexp.setAttribute("pattern", "/BUILD FAILED/");
              regexp.setAttribute("subject", subject);
              regexp.setAttribute("to", to);
              regexp.setAttribute("from", from);
              nag.appendChild(regexp);
  
          } else {
  
              Node child = nag.getFirstChild();
              for (; child != null; child = child.getNextSibling()) {
                  if (child.getNodeName().equals("regexp")) {
                      Element regexp = (Element)child;
                      if (regexp.getAttribute("pattern").equals("")) {
                          regexp.setAttribute("pattern", "/BUILD FAILED/");
                      }
  
                      if (regexp.getAttribute("subject").equals("")) {
                          regexp.setAttribute("subject", subject);
                      } else {
                          String orig = regexp.getAttribute("subject");
                          regexp.setAttribute("subject", nagPrefix + " " + orig);
                          
                      }
  
                      if (nagTo != null 
                          || regexp.getAttribute("to").equals("")) {
                          regexp.setAttribute("to", to);
                      }
  
                      if (regexp.getAttribute("from").equals("")) {
                          regexp.setAttribute("from", from);
                      }
                  }
              }
  
          }
      }
  }
  
  
  
  1.1                  
jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Repository.java
  
  Index: Repository.java
  ===================================================================
  /*
   * $Header: 
/home/cvs/jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Repository.java,v
 1.1 2002/09/02 13:48:43 nicolaken Exp $
   * $Revision: 1.1 $
   * $Date: 2002/09/02 13:48:43 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  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 the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Alexandria", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * 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 THE APACHE SOFTWARE FOUNDATION 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 the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
   
  package org.apache.alexandria.jenny;
   
  // DOM classes
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  
  // Java classes
  import java.util.Enumeration;
  import java.util.Vector;
  import java.util.Hashtable;
  
  public class Repository {
  
      static private Hashtable repositories = new Hashtable();
      private Element element;
      private String name;
  
      /**
       * Create a set of Repository definitions based on XML nodes.
       * @param moudules list of &lt;module&gt; elements
       */
      public static void load(Enumeration repositories) throws Exception {
          while (repositories.hasMoreElements()) {
              new Repository((Element)repositories.nextElement());
          }
      }
  
      /**
       * Find a Repository given its name.
       * @param name module name
       * @return Repository
       */
      public static Repository find(String name) {
          return (Repository) repositories.get(name);
      }
  
      /**
       * Constructor.
       * @param element &lt;module&gt; element
       */
      public Repository(Element element) throws Exception {
          this.element = element;
          name = element.getAttribute("name");
  
          resolve(element);
  
          repositories.put(name, this);
      }
  
      /**
       * General attribute accessor.
       * @param name attribute name
       * @return Value of the specified attribute.
       */
      public String get(String name) {
          return element.getAttribute(name);
      }
  
      /**
       * Use text nodes under root elements to fill in defaults for repository
       * attributes.
       */
      private void resolve(Node node) throws Exception {
          // recurse through all children
          Node child=node.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              resolve(child);
          }
  
          // <parent>text<parent> => <element parent="text">
          if (node.getNodeType() != Node.TEXT_NODE) return;
  
          Node parent = node.getParentNode();
          Node gparent = parent.getParentNode();
          if (!gparent.getNodeName().equals("root")) return;
  
          if (element.getAttributeNode(parent.getNodeName()) == null) {
              element.setAttribute(parent.getNodeName(), node.getNodeValue());
          }
      }
  
  }
  
  
  
  1.1                  
jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Server.java
  
  Index: Server.java
  ===================================================================
  /*
   * $Header: 
/home/cvs/jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Server.java,v 
1.1 2002/09/02 13:48:43 nicolaken Exp $
   * $Revision: 1.1 $
   * $Date: 2002/09/02 13:48:43 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  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 the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Alexandria", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * 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 THE APACHE SOFTWARE FOUNDATION 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 the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
   
  package org.apache.alexandria.jenny;
   
  // DOM classes
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  
  // Java classes
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Iterator;
  
  public class Server {
  
      private static HashMap sites = new HashMap();
  
      /**
       * Create a set of site definitions based on XML nodes.
       * @param elements list of &lt;server&gt; elements
       */
      public static void load(Enumeration elements) throws Exception {
          Element element = null;
          while (elements.hasMoreElements()) {
              try {
                  element = (Element)elements.nextElement();
                  new Server(element);
              } catch (Throwable t) {
                  System.err.println("Dropping server "
                                     + element.getAttribute("name")
                                     + " because of Exception " + t);
              }
          }
      }
  
      /**
       * Static accessor for the named site
       * @return the element corresponding to the site, if it is defined
       * in a server document as well as in the workspace.
       */
      public static Element getSite(String name) {
          return (Element) sites.get(name);
      }
  
      /**
       * Constructor
       *
       * <p>Promotes all &lt;site&gt; children to workspace, if the site
       * is defined in the workspace's deliver element.  Otherwise,
       * simply remove the site child element.</p>
       */
      private Server(Element element) {
          String name = element.getAttribute("name");
          ArrayList children = new ArrayList();
          Node child = element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (child.getNodeName().equals("site")) {
                  Element site = (Element) child;
                  site.setAttribute("server", name);
                  String siteName = site.getAttribute("name");
                  if (Workspace.getServer(name) != null 
                      && Workspace.isSiteDefined(siteName)) {
                      sites.put(siteName, site);
                  }
                  children.add(site);
              }
          }
              
          Iterator iter = children.iterator();
          Node parent = element.getParentNode();
          while (iter.hasNext()) {
              Element site = (Element) iter.next();
              element.removeChild(site);
  
              Element server = Workspace.getServer(name);
              if (server != null 
                  && Workspace.isSiteDefined(site.getAttribute("name"))) {
                  addServerAttributes(server, site);
                  parent.appendChild(site);
              }
          }
      }
  
      /**
       * Adds attributes defined in Workspace to the site element.
       *
       * @param server server element from workspace
       * @param site site element from server
       */
      private void addServerAttributes(Element server, Element site) {
          site.setAttribute("scratchdir", Workspace.getScratchDir());
          site.setAttribute("username", 
                            server.getAttribute("username"));
  
          String drop = server.getAttribute("dropdir");
          if (drop.equals("")) {
              drop = "/tmp";
          }
          site.setAttribute("dropdir", drop);
      }
  
  }
  
  
  
  1.1                  
jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Workspace.java
  
  Index: Workspace.java
  ===================================================================
  /*
   * $Header: 
/home/cvs/jakarta-alexandria/jenny/src/java/org/apache/alexandria/jenny/Workspace.java,v
 1.1 2002/09/02 13:48:43 nicolaken Exp $
   * $Revision: 1.1 $
   * $Date: 2002/09/02 13:48:43 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2002 The Apache Software Foundation.  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 the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Alexandria", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * 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 THE APACHE SOFTWARE FOUNDATION 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 the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
   
  package org.apache.alexandria.jenny;
   
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  
  import java.util.HashMap;
  
  public class Workspace {
  
      static final String GUMP_VERSION = "0.3";
  
      private static Element element;
      private static String basedir;
      private static Element javadoc;
      private static Element deliver;
      private static Element nag;
      private static HashMap servers = new HashMap();
      private static HashMap sites = new HashMap();
  
      /**
       * Static property accessor for basedir attribute.
       * @return Base directory associated with this workspace
       */
      public static String getBaseDir() {
          return element.getAttribute("basedir");
      }
  
      /**
       * Static property accessor for pkgdir attribute.
       * @return Package directory associated with this workspace
       */
      public static String getPkgDir() {
          return element.getAttribute("pkgdir");
      }
  
      /**
       * Static property accessor for jardir attribute.
       * @return Package directory associated with this workspace
       */
      public static String getJarDir() {
          return element.getAttribute("jardir");
      }
  
      /**
       * Static property accessor for javadoc element.
       * @return Javadoc element (if any) associated with this workspace
       */
      public static Element getJavaDoc() {
          return javadoc;
      }
  
      /**
       * Static property accessor for nag element.
       * @return Javadoc element (if any) associated with this workspace
       */
      public static Element getNag() {
          return nag;
      }
  
      /**
       * Static accessor - do we deliver to the given site?
       * @return true if a server of the given name is defined in the workspace.
       */
      public static boolean isSiteDefined(String name) {
          return sites.containsKey(name);
      }
  
      /**
       * Static accessor for deliver's scratchdir attribute.
       * @return Deliver scratchdir associated with this workspace.
       */
      public static String getScratchDir() {
          if (deliver != null) {
              return deliver.getAttribute("scratchdir");
          }
          return null;
      }
  
      /**
       * Static accessor for deliver's named server child.
       * @return null if server is not defined, the server element otherwise.
       */
      public static Element getServer(String name) {
          return (Element) servers.get(name);
      }
  
      /**
       * Default and verify various workspace attributes.
       * If not specified:
       *   banner-image="http://jakarta.apache.org/images/jakarta-logo.gif";
       *   banner-link="http://jakarta.apache.org";
       *   cvsdir=basedir+"/cvs"
       *   logdir=basedir+"/log"
       *   pkgdir=basedir
       * @param Workspace element to be updated
       */
      public static void init(Element workspace) throws Exception {
          Workspace.element = workspace;
  
          if (!workspace.getAttribute("version").equals(GUMP_VERSION)) {
              throw new Exception("workspace version " + GUMP_VERSION +
                                  " required.");
          }
  
          basedir = workspace.getAttribute("basedir");
  
          if (workspace.getAttribute("banner-image").equals("")) {
              workspace.setAttribute("banner-image",
                  "http://jakarta.apache.org/images/jakarta-logo.gif";);
          }
  
          if (workspace.getAttribute("banner-link").equals("")) {
              workspace.setAttribute("banner-link", "http://jakarta.apache.org";);
          }
  
          if (workspace.getAttribute("logdir").equals("")) {
              workspace.setAttribute("logdir", basedir + "/log");
          }
  
          if (workspace.getAttribute("cvsdir").equals("")) {
              workspace.setAttribute("cvsdir", basedir + "/cvs");
          }
  
          if (workspace.getAttribute("pkgdir").equals("")) {
              workspace.setAttribute("pkgdir", basedir);
          }
  
          Node child=element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (child.getNodeName().equals("javadoc")) {
                  javadoc = (Element) child;
              } else if (child.getNodeName().equals("nag")) {
                  nag = (Element) child;
              } else if (child.getNodeName().equals("deliver")) {
                  handleDeliver((Element) child);
              }
          }
          
          if (deliver != null) {
              element.removeChild(deliver);
              String scratchdir = deliver.getAttribute("scratchdir");
              if (scratchdir.equals("")) {
                  deliver.setAttribute("scratchdir", basedir+"/scratch");
              }
          }
          
      }
  
      /**
       * Fill map of servers.
       */
      private static void handleDeliver(Element element) {
          deliver = element;
          Node child=element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (child.getNodeName().equals("server")) {
                  Element server = (Element) child;
                  servers.put(server.getAttribute("name"), server);
                  handleSites(server);
              }
          }
      }
  
      /**
       * Fill map of sites.
       */
      private static void handleSites(Element element) {
          Node child=element.getFirstChild();
          for (; child != null; child=child.getNextSibling()) {
              if (child.getNodeName().equals("site")) {
                  Element site = (Element) child;
                  sites.put(site.getAttribute("name"), site);
              }
          }
      }    
  }
  
  
  

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to