ant-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bode...@apache.org
Subject cvs commit: ant/src/main/org/apache/tools/ant/util ClasspathUtils.java
Date Wed, 23 Apr 2003 15:03:06 GMT
bodewig     2003/04/23 08:03:06

  Modified:    src/etc/testcases/core/loaderref loaderref.xml
               src/main/org/apache/tools/ant/taskdefs Definer.java
               src/main/org/apache/tools/ant/util ClasspathUtils.java
  Log:
  Make Definer use the new ClasspathUtils, make ClasspathUtils even more
  reuse-friendly.
  
  PR: 19213
  Submitted by:	Marc Portier <mpo at apache dot org>
  
  Revision  Changes    Path
  1.2       +1 -0      ant/src/etc/testcases/core/loaderref/loaderref.xml
  
  Index: loaderref.xml
  ===================================================================
  RCS file: /home/cvs/ant/src/etc/testcases/core/loaderref/loaderref.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- loaderref.xml	6 Jan 2003 13:58:31 -0000	1.1
  +++ loaderref.xml	23 Apr 2003 15:03:06 -0000	1.2
  @@ -22,6 +22,7 @@
   
     <target name="testbadref" depends="compile" >
       <taskdef loaderref="loaderref-test"
  +             name="test1"
                classname="Test1"
                classpath="${classes.dir}"/>
     </target>
  
  
  
  1.29      +23 -93    ant/src/main/org/apache/tools/ant/taskdefs/Definer.java
  
  Index: Definer.java
  ===================================================================
  RCS file: /home/cvs/ant/src/main/org/apache/tools/ant/taskdefs/Definer.java,v
  retrieving revision 1.28
  retrieving revision 1.29
  diff -u -r1.28 -r1.29
  --- Definer.java	7 Mar 2003 11:23:01 -0000	1.28
  +++ Definer.java	23 Apr 2003 15:03:06 -0000	1.29
  @@ -66,6 +66,7 @@
   import org.apache.tools.ant.Task;
   import org.apache.tools.ant.types.Path;
   import org.apache.tools.ant.types.Reference;
  +import org.apache.tools.ant.util.ClasspathUtils;
   
   /**
    * Base class for Taskdef and Typedef - does all the classpath
  @@ -79,21 +80,16 @@
   public abstract class Definer extends Task {
       private String name;
       private String value;
  -    private Path classpath;
       private File file;
       private String resource;
  -    private boolean reverseLoader = false;
  -    private String loaderId = null;
  -    private String classpathId = null;
  -
  -    private static final String REUSE_LOADER_REF = "ant.reuse.loader";
  +    private ClasspathUtils.Delegate cpDelegate;
   
       /**
        * @deprecated stop using this attribute
        * @ant.attribute ignore="true"
        */
       public void setReverseLoader(boolean reverseLoader) {
  -        this.reverseLoader = reverseLoader;
  +        this.cpDelegate.setReverseLoader(reverseLoader);
           log("The reverseloader attribute is DEPRECATED. It will be removed",
               Project.MSG_WARN);
       }
  @@ -103,7 +99,7 @@
       }
   
       public Path getClasspath() {
  -        return classpath;
  +        return cpDelegate.getClasspath();
       }
   
       public File getFile() {
  @@ -115,15 +111,15 @@
       }
   
       public boolean isReverseLoader() {
  -        return reverseLoader;
  +        return cpDelegate.isReverseLoader();
       }
   
       public String getLoaderId() {
  -        return loaderId;
  +        return cpDelegate.getClassLoadId();
       }
   
       public String getClasspathId() {
  -        return classpathId;
  +        return cpDelegate.getClassLoadId();
       }
   
       /**
  @@ -132,21 +128,14 @@
        * @param classpath an Ant Path object containing the classpath.
        */
       public void setClasspath(Path classpath) {
  -        if (this.classpath == null) {
  -            this.classpath = classpath;
  -        } else {
  -            this.classpath.append(classpath);
  -        }
  +        this.cpDelegate.setClasspath(classpath);
       }
   
       /**
        * Create the classpath to be used when searching for component being defined
        */
       public Path createClasspath() {
  -        if (this.classpath == null) {
  -            this.classpath = new Path(getProject());
  -        }
  -        return this.classpath.createPath();
  +        return this.cpDelegate.createClasspath();
       }
   
       /**
  @@ -154,8 +143,7 @@
        * To actually share the same loader, set loaderref as well
        */
       public void setClasspathRef(Reference r) {
  -        classpathId=r.getRefId();
  -        createClasspath().setRefid(r);
  +        this.cpDelegate.setClasspathref(r);
       }
   
       /**
  @@ -170,7 +158,7 @@
        * @since Ant 1.5
        */
       public void setLoaderRef(Reference r) {
  -        loaderId = r.getRefId();
  +        this.cpDelegate.setLoaderRef(r);
       }
   
   
  @@ -272,81 +260,12 @@
        * create a classloader for this definition
        */
       private ClassLoader createLoader() {
  -        // magic property
  -        if (getProject().getProperty(REUSE_LOADER_REF) != null) {
  -            // Generate the 'reuse' name automatically from the reference.
  -            // This allows <taskdefs> that work on both ant1.4 and ant1.5.
  -            // ( in 1.4 it'll require the task/type to be in classpath if they
  -            //   are used togheter ).
  -            if (loaderId == null && classpathId != null) {
  -                loaderId = "ant.loader." + classpathId;
  -            }
  -        }
  -
  -        // If a loader has been set ( either by loaderRef or magic property )
  -        if (loaderId != null) {
  -            Object reusedLoader = getProject().getReference(loaderId);
  -            if (reusedLoader != null) {
  -                if (!(reusedLoader instanceof ClassLoader)) {
  -                    throw new BuildException("The specified loader id " +
  -                        loaderId + " does not reference a class loader");
  -                }
  -
  -                return (ClassLoader)reusedLoader;
  -                //if (reusedLoader instanceof AntClassLoader) {
  -                //    return (AntClassLoader)reusedLoader;
  -                //}
  -                // In future the reference object may be the <loader> type
  -                // if (reusedLoader instanceof Loader ) {
  -                //      return ((Loader)reusedLoader).getLoader(project);
  -                // }
  -            }
  -        }
  -
  -        ClassLoader al = null;
  -
  -        if (classpath == null) {
  -            // do we need to create another loader ?
  -            al=project.getCoreLoader();
  -            if (al != null ) {
  -                return al;
  -            }
  -        }
  -
  -        if (classpath != null) {
  -            project.log( "Creating new loader for taskdef using " + classpath +
  -                    " reverse=" + reverseLoader, Project.MSG_DEBUG );
  -            AntClassLoader acl = getProject().createClassLoader(classpath);
  -            if (reverseLoader) {
  -                acl.setParentFirst(false);
  -                acl.addJavaLibraries();
  -            }
  -            al = acl;
  -        } else {
  -            // XXX Probably it would be better to reuse getClass().getClassLoader()
  -            // I don't think we need a new ( identical ) loader for each task
  -            AntClassLoader acl
  -                = getProject().createClassLoader(Path.systemClasspath);
  -            if (reverseLoader) {
  -                acl.setParentFirst(false);
  -                acl.addJavaLibraries();
  -            }
  -            al = acl;
  -        }
  +        ClassLoader al = this.cpDelegate.getClassLoader();
           // need to load Task via system classloader or the new
           // task we want to define will never be a Task but always
           // be wrapped into a TaskAdapter.
           ((AntClassLoader)al).addSystemPackageRoot("org.apache.tools.ant");
   
  -
  -        // If the loader is new, record it for future uses by other
  -        // task/typedefs
  -        if (loaderId != null) {
  -            if (getProject().getReference(loaderId) == null) {
  -                getProject().addReference(loaderId, al);
  -            }
  -        }
  -
           return al;
       }
   
  @@ -396,4 +315,15 @@
        * they will get to add a new definition of their type.
        */
       protected abstract void addDefinition(String name, Class c);
  +    
  +    
  +    /**
  +     * @see org.apache.tools.ant.Task#init()
  +     * @since Ant 1.6
  +     */
  +    public void init() throws BuildException {
  +        this.cpDelegate = ClasspathUtils.getDelegate(this);
  +        super.init();
  +    }
  +
   }
  
  
  
  1.2       +318 -69   ant/src/main/org/apache/tools/ant/util/ClasspathUtils.java
  
  Index: ClasspathUtils.java
  ===================================================================
  RCS file: /home/cvs/ant/src/main/org/apache/tools/ant/util/ClasspathUtils.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ClasspathUtils.java	14 Apr 2003 07:06:55 -0000	1.1
  +++ ClasspathUtils.java	23 Apr 2003 15:03:06 -0000	1.2
  @@ -56,6 +56,7 @@
   import org.apache.tools.ant.AntClassLoader;
   import org.apache.tools.ant.BuildException;
   import org.apache.tools.ant.Project;
  +import org.apache.tools.ant.ProjectComponent;
   import org.apache.tools.ant.types.Path;
   import org.apache.tools.ant.types.Reference;
   
  @@ -63,35 +64,38 @@
    * Offers some helper methods on the Path structure in ant.
    *
    * <p>Basic idea behind this utility class is to use it from inside the 
  - * different ant objects (and user defined objects) that need dclassLoading 
  + * different ant objects (and user defined objects) that need classLoading 
    * for their operation.
  - * Normally those would have a setClasspathRef() {for the @@classpathref} 
  + * Normally those would have a setClasspathRef() {for the @classpathref} 
    * and/or a createClasspath() {for the nested &lt;classpath&gt;}
  - * Typically one would have</p> 
  + * Typically one would have in your Ant Task or DataType</p> 
    *
    * <pre><code>
  + * ClasspathUtils.Delegate cpDelegate;
  + * 
  + * public void init(){
  + *     this.cpDelegate = ClasspathUtils.getDelegate(this);
  + *     super.init();
  + * }
  + * 
    * public void setClasspathRef(Reference r) {
  - *     this.classpathId = r.getRefId();
  - *     createClasspath().setRefid(r);
  + *     this.cpDelegate.setClasspathRef(r);
    * }
    * 
    * public Path createClasspath() {
  - *     if (this.classpath == null) {
  - *         this.classpath = new Path(getProject());
  - *     }
  - *     return this.classpath.createPath();
  + *     return this.cpDelegate.createClasspath();
    * }
    * 
    * public void setClassname(String fqcn) {
  - *     this.classname = fqcn;
  + *     this.cpDelegate.setClassname(fqcn);
    * }
    * </code></pre>
    * 
  - * <p>when you actually need the classloading you can just:</p>
  + * <p>At execution time, when you actually need the classloading 
  + * you can just:</p>
    * 
    * <pre><code>
  - *     ClassLoader cl = ClasspathUtils.getClassLoaderForPath(this.classpath, this.classpathId);
  - *     Object o = ClasspathUtils.newInstance(this.classname, cl);
  + *     Object o = this.cpDelegate.newInstance();
    * </code></pre>
    *
    * @since Ant 1.6
  @@ -110,12 +114,14 @@
        * @param pathId
        * @return
        */
  -    public static ClassLoader getClassLoaderForPath(Project p, Reference ref) {
  +    public static ClassLoader getClassLoaderForPath(
  +        Project p, Reference ref) {
  +
           return getClassLoaderForPath(p, ref, false);
       }
  -    
  +
       /** 
  -     * Convenience overloaded version of {@link #geClassLoader(Path,
  +     * Convenience overloaded version of {@link #geClassLoader(Project, Path,
        * String, boolean)}.
        *
        * <p>Delegates to the other one after extracting the referenced
  @@ -128,31 +134,54 @@
        * classloader behaviour)
        * @return
        */
  -    public static ClassLoader getClassLoaderForPath(Project p, Reference ref, 
  -                                                    boolean reverseLoader) {
  +    public static ClassLoader getClassLoaderForPath(
  +        Project p, Reference ref, boolean reverseLoader) {
  +            
           String pathId = ref.getRefId();
           Object path = p.getReference(pathId);
  -        if (!(path instanceof Path)){
  -            throw new BuildException ("The specified classpathref " + pathId + 
  -                " does not reference a Path.");
  -        }        
  -        return getClassLoaderForPath((Path)path, pathId, reverseLoader);
  +        if (!(path instanceof Path)) {
  +            throw new BuildException(
  +                "The specified classpathref "
  +                    + pathId
  +                    + " does not reference a Path.");
  +        }
  +        String loaderId = LOADER_ID_PREFIX + pathId;
  +        return getClassLoaderForPath(p, (Path) path, loaderId, reverseLoader);
       }
   
       /** 
        * Convenience overloaded version of {@link
  -     * #getClassLoaderForPath(Path, String, boolean)}.
  +     * #getClassLoaderForPath(Project, Path, String, boolean)}.
        *
        * <p>Assumes the logical 'false' for the reverseLoader.</p>
        *  
        * @param path 
  -     * @param pathId
  +     * @param loaderId
        * @return
        */
  -    public static ClassLoader getClassLoaderForPath(Path path, String pathId){
  -        return getClassLoaderForPath(path, pathId, false);    
  +    public static ClassLoader getClassLoaderForPath(
  +        Project p, Path path, String loaderId) {
  +
  +        return getClassLoaderForPath(p, path, loaderId, false);
       }
  -    
  +
  +    /** 
  +     * Convenience overloaded version of {@link
  +     * #getClassLoaderForPath(Project, Path, String, boolean, boolean)}.
  +     *
  +     * <p>Sets value for 'reuseLoader' to true if the magic property
  +     * has been set.</p>
  +     *  
  +     * @param path 
  +     * @param loaderId
  +     * @return
  +     */
  +    public static ClassLoader getClassLoaderForPath(
  +        Project p, Path path, String loaderId, boolean reverseLoader) {
  +        return getClassLoaderForPath(p, path, loaderId, reverseLoader,
  +                                     isMagicPropertySet(p));
  +    }
  +
       /**
        * Gets a classloader that loads classes from the classpath
        * defined in the path argument.
  @@ -162,35 +191,38 @@
        * created loader with that id, and of course store it there upon
        * creation.</p>
        * @param path Path object to be used as classpath for this classloader
  -     * @param pathId identification for this Path, will be used to
  -     * identify the classLoader as well.
  +     * @param loaderID identification for this Loader, 
        * @param reverseLoader if set to true this new loader will take
        * precedence over it's parent (which is contra the regular
  +     * @param p Ant Project where the handled components are living in. 
        * classloader behaviour)
        * @return ClassLoader that uses the Path as its classpath. 
        */
  -    public static ClassLoader getClassLoaderForPath(Path path, String pathId, 
  -                                                    boolean reverseLoader) {
  +    public static ClassLoader getClassLoaderForPath(
  +        Project p, Path path, String loaderId, boolean reverseLoader,
  +        boolean reuseLoader) {
  +
           ClassLoader cl = null;
  -        Project p = path.getProject();
  -        String loaderId = LOADER_ID_PREFIX + pathId;
  -        // code stolen from o.a.t.a.taskdefs.Definer, might be a todo
  -        // to remove it there didn't look at the reverse loader stuff
  -        // however (todo that first)
   
           // magic property
  -        if (p.getProperty(REUSE_LOADER_REF) != null) {
  -            //chose not to do the extra instanceof checking here, consider it 
  -            // a programming error and not a config error if this fails
  -            // so I assume the RuntimeException is OK 
  -            cl = (ClassLoader)p.getReference(loaderId);
  -        }
  -        if (cl == null){
  -            cl = getUniqueClassLoaderForPath(path, reverseLoader);
  -            p.addReference(loaderId, cl);            
  -        } 
  -        
  -        return cl;        
  +        if (loaderId != null && reuseLoader) {
  +            Object reusedLoader = p.getReference(loaderId);
  +            if (reusedLoader != null && 
  +                !(reusedLoader instanceof ClassLoader)) {
  +                throw new BuildException("The specified loader id " + loaderId +
  +                                         " does not reference a class loader");
  +            }
  +
  +            cl = (ClassLoader) reusedLoader;
  +        }
  +        if (cl == null) {
  +            cl = getUniqueClassLoaderForPath(p, path, reverseLoader);
  +            if (loaderId != null && reuseLoader) {
  +                p.addReference(loaderId, cl);
  +            }
  +        }
  +
  +        return cl;
       }
   
       /**
  @@ -203,39 +235,256 @@
        * @param reverseLoader
        * @return
        */
  -    public static ClassLoader getUniqueClassLoaderForPath(Path path,
  -                                                          boolean reverseLoader) {
  -        ClassLoader cl = null;
  -        Project p = path.getProject();
  +    public static ClassLoader getUniqueClassLoaderForPath(
  +        Project p,
  +        Path path,
  +        boolean reverseLoader) {
   
  -        AntClassLoader acl = p.createClassLoader(Path.systemClasspath);
  +        AntClassLoader acl = p.createClassLoader(path != null 
  +                                                 ? path : Path.systemClasspath);
           if (reverseLoader) {
               acl.setParentFirst(false);
               acl.addJavaLibraries();
           }
   
  -        return cl;        
  +        return acl;
       }
  -    
  -    public static Object newInstance(String className,
  -                                     ClassLoader userDefinedLoader){
  +
  +    /**
  +     * Creates a fresh object instance of the specified classname.
  +     * 
  +     * <p> This uses the userDefinedLoader to load the specified class, 
  +     * and then makes an instance using the default no-argument constructor
  +     * </p>
  +     * 
  +     * @param className the full qualified class name to load.
  +     * @param userDefinedLoader the classloader to use.
  +     * @return
  +     * @throws BuildException when loading or instantiation failed.
  +     */
  +    public static Object newInstance(
  +        String className,
  +        ClassLoader userDefinedLoader) {
           try {
               Class clazz = userDefinedLoader.loadClass(className);
  -            Object o = clazz.newInstance();        
  -            return o; 
  +            Object o = clazz.newInstance();
  +            return o;
           } catch (ClassNotFoundException e) {
  -            throw new BuildException("Class " + className + 
  -                                     " not found by the specific classLoader.",
  -                                     e);
  +            throw new BuildException(
  +                "Class "
  +                    + className
  +                    + " not found by the specific classLoader.",
  +                e);
           } catch (InstantiationException e) {
  -            throw new BuildException("Could not instantiate " + className 
  -                                     + ". Specified class should have a no "
  -                                     + "argument constructor.", e);
  +            throw new BuildException(
  +                "Could not instantiate "
  +                    + className
  +                    + ". Specified class should have a no "
  +                    + "argument constructor.",
  +                e);
           } catch (IllegalAccessException e) {
  -            throw new BuildException("Could not instantiate " + className 
  -                                     + ". Specified class should have a "
  -                                     + "public constructor.", e);
  +            throw new BuildException(
  +                "Could not instantiate "
  +                    + className
  +                    + ". Specified class should have a "
  +                    + "public constructor.",
  +                e);
           }
       }
   
  +    /**
  +     * Obtains a delegate that helps out with classic classpath configuration.
  +     * 
  +     * @param component your projectComponent that needs the assistence
  +     * @return the helper, delegate.
  +     * @see ClasspathUtils.Delegate
  +     */
  +    public static Delegate getDelegate(ProjectComponent component) {
  +        return new Delegate(component);
  +    }
  +
  +    /**
  +     * Checks for the magic property that enables class loader reuse
  +     * for <taskdef> and <typedef> in Ant 1.5 and earlier.
  +     */
  +    private static boolean isMagicPropertySet(Project p) {
  +        return p.getProperty(REUSE_LOADER_REF) != null;
  +    }
  +
  +    /** 
  +     * Delegate that helps out any specific ProjectComponent that needs
  +     * dynamic classloading.
  +     *  
  +     * <p>Ant ProjectComponents that need a to be able to dynamically load
  +     * Classes and instantiate them often expose the following ant syntax
  +     * sugar: </p>
  +     * 
  +     * <ul><li> nested &lt;classpath&gt; </li>
  +     * <li> attribute @classpathref </li>
  +     * <li> attribute @classname </li></ul>
  +     * 
  +     * <p> This class functions as a delegate handling the configuration
  +     * issues for this recuring pattern.  It's usage pattern, as the name 
  +     * suggests is delegation, not inheritance. </p>
  +     * 
  +     * @since Ant 1.6
  +     */
  +    public static class Delegate {
  +        private final ProjectComponent component;
  +        private Path classpath;
  +        private String classpathId;
  +        private String className;
  +        private String loaderId;
  +        private boolean reverseLoader = false;
  +        
  +        /**
  +         * Constructs Delegate
  +         * @param component
  +         */
  +        Delegate(ProjectComponent component) {
  +            this.component = component;
  +        }
  +        
  +        /**
  +         * Delegate method handling the @classpath attribute
  +         * 
  +         * <p>This attribute can set a path to add to the classpath</p> 
  +         * 
  +         * @param classpath
  +         */
  +        public void setClasspath(Path classpath) {
  +            if (this.classpath == null) {
  +                this.classpath = classpath;
  +            } else {
  +                this.classpath.append(classpath);
  +            }
  +        }
  +        
  +        /** 
  +         * Delegate method handling the &lt;classpath&gt; tag.
  +         * 
  +         * <p>This nested path-like structure can set a path to add to the
  +         * classpath</p>
  +         * 
  +         * @return
  +         */
  +        public Path createClasspath() {
  +            if (this.classpath == null) {
  +                this.classpath = new Path(component.getProject());
  +            }
  +            return this.classpath.createPath();
  +        }
  +        
  +        /**
  +         * Delegate method handling the @classname attribute.
  +         * 
  +         * <p>This attribute sets the full qualified class name of the class
  +         * to lad and instantiate</p>
  +         * 
  +         * @param fcqn
  +         */
  +        public void setClassname(String fcqn) {
  +            this.className = fcqn;
  +        }
  +        
  +        /** 
  +         * Delegate method handling the @classpathref attribute.
  +         * 
  +         * <p>This attribute can add a referenced path-like structure to the
  +         * classpath</p> 
  +         * 
  +         * @param r
  +         */
  +        public void setClasspathref(Reference r) {
  +            this.classpathId = r.getRefId();
  +            createClasspath().setRefid(r);
  +        }        
  +        
  +        /**
  +         * Delegate method handling the @reverseLoader attribute.
  +         * 
  +         * <p>This attribute can set a boolean indicating that the used 
  +         * classloader should NOT follow the classical parent-first scheme.
  +         * </p>
  +         * 
  +         * <p>By default this is supposed to be false</p>
  +         * 
  +         * <p>Caution: this behaviour is contradictory to the normal way
  +         * classloaders work.  Do not let your ProjectComponent use it if
  +         * you are not really sure</p> 
  +         * 
  +         * @param reverseLoader
  +         */
  +        public void setReverseLoader(boolean reverseLoader) {
  +            this.reverseLoader = reverseLoader;
  +        }
  +
  +        /**
  +         * Sets the loaderRef 
  +         * @param r
  +         */
  +        public void setLoaderRef(Reference r) {
  +            this.loaderId = r.getRefId();
  +        }
  +            
  +
  +        /** 
  +         * Finds or creates the classloader for this
  +         * @return
  +         */
  +        public ClassLoader getClassLoader() {
  +            ClassLoader cl;
  +            cl= ClasspathUtils.getClassLoaderForPath(
  +                    getContextProject(),
  +                    this.classpath,
  +                    getClassLoadId(),
  +                    this.reverseLoader,
  +                    loaderId != null 
  +                    || isMagicPropertySet(getContextProject()));
  +            return cl;
  +        }
  +                
  +        /**
  +         * The project of the ProjectComponent we are working for.
  +         */
  +        private Project getContextProject() {
  +            return this.component.getProject();            
  +        }
  +
  +        /**
  +         * Computes the loaderId based on the configuration of the component.
  +         */
  +        public String getClassLoadId() {
  +            if (this.loaderId == null && this.classpathId != null) {
  +                return ClasspathUtils.LOADER_ID_PREFIX + this.classpathId;
  +            } else {
  +                return this.loaderId;
  +            }            
  +        }
  +
  +        /** 
  +         * Helper method obtaining a fresh instance of the class specified
  +         * in the @classname and using the specified classpath.
  +         *   
  +         * @return the fresh instantiated object.
  +         */
  +        public Object newInstance() {
  +            ClassLoader cl = getClassLoader();
  +            return ClasspathUtils.newInstance(this.className, cl);
  +        }
  +
  +        /**
  +         * The classpath.
  +         */
  +        public Path getClasspath() {
  +            return classpath;
  +        }
  +
  +        public boolean isReverseLoader() {
  +            return reverseLoader;
  +        }
  +
  +        //TODO no methods yet for getClassname
  +        //TODO no method for newInstance using a reverse-classloader
  +    }
   }
  
  
  

Mime
View raw message