Wow.  That's a lot of comments, all good ones.  I'd hate to see you
sad, Henning, your cheery disposition lights up this project.  :-)

One quick point.  I want to re-iterate what I said in the JIRA note.
This is a bug that's been in Velocity a long time, in a fundamental
part of the caching routine.  Nice job, Alexey, on finding this,
fixing it, and jumping through all the hoops to get the patch in.
Hope to see more.

I'll take responsibility to work with Alexey in cleaning this up.
Though to be fair, you are holding the patch to a higher standard than
the original.  Of course the original had a bug.

And the typo in the copyright date is my fault.  Legal issues are
really the responsibility of the committers :-)

WILL


On 9/27/06, Henning P. Schmiedehausen <[EMAIL PROTECTED]> wrote:
[EMAIL PROTECTED] writes:

(This mail might sound a bit hash. It is not intended as such, but I
want to make sure that we do get peer review with code going in, even
if it is "just a small fix". So I intend to use this as an example
case. Sorry 'bout that. This message is intended to criticize the
code, not you or Alexey).

>-    private String methodName = "";
>+    protected String methodName = "";

[...]

>+                if (params.length == other.params.length && 
methodName.equals(other.getASTMethod().methodName))

I'm -0.5 on that patch. That coding style is not good (private is
there for a reason), the change to protected is not even needed,
because if you are an object of class "Foo", you can look at the
private fields of every other Foo, too:

public class Foo {
     private final int value;

    public Foo(final int value) { this.value = value; }

    public void report(final Foo foo) {
        System.out.println(foo.value);
    }
}


public class Bar {
    public static void main(String[] args) {
        Foo foo1 = new Foo(23);
        Foo foo2 = new Foo(42);
        foo2.report(foo1);
    }
}

Run "Bar" and you will get "23" as the result. foo2 is allowed to look
at the private value field of foo1.

If we need to look at member fields of any class, we add getters and
setters, not change the visibility of the member fields. It is the job
of the Java compiler (which does a very good job here) to optimize
that.

I don't like the fact that there is object comparing (the Class
elements of params should be compared using equals, not != ) in the
code.

The code doesn't adhere to our indent rules and the year date in the
copyright notice is wrong (should be 2006). I freely admit that I'm
picking nits here, mainly because I don't like the implementation of
the methods and don't believe in the handwaving that "this is faster
than using EqualsBuilder and HashCodeBuilder because of Object
creation". I haven't seen any numbers to prove or disprove that claim.

The hash method uses the ^= method to add the parameters. This is a
really bad idea, because it detoriates the hash value. Consider two
arrays:

Class [] e1 = new Class [] { String.class, String.class };
Class [] e2 = new Class [] { Object.class, Object.class };

int result = 0;
for (int i = 0; i < params.length; ++i)
{
    final Class param = params[i];
    if (param != null)
    {
        result ^= param.hashCode();
    }
}

That loop returns the same value for both arrays: 0 (The reason why is
left for the reader as an exercise :-) ).

Yes, it *is* hard to write a good hash function. That is why there is
so much literature about it.
E.g. http://java.sun.com/developer/Books/effectivejava/Chapter3.pdf

(In "Hard Core Java", the methods in the Jakarta commons are
explicitly mentioned as examples for a good hash code function. There
is a reason for this).

I know that "this is private code (if it is, why is MethodCacheKey
package protected and not private?)" and "it can never be called with
params == null". However, if you do, you get an NPE. If it is possible
to make that code robust, why not do it?

An unit test draft could look like this:

--- cut ---
package org.apache.velocity.runtime.parser.node;

import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.commons.lang.ArrayUtils;
import org.apache.velocity.runtime.parser.node.ASTMethod.MethodCacheKey;
import org.apache.velocity.test.BaseTestCase;

/*
 * Copyright 2006 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Test ASTMethod equals() and hashCode()
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Will Glass-Husain</a>
 * @version $Id: ResourceCachingTestCase.java 447969 2006-09-19 21:00:14Z 
henning $
 */
public class MethodKeyTestCase extends BaseTestCase
{
    /**
     * Default constructor.
     */
    public MethodKeyTestCase(String name)
    {
        super(name);
    }

    public void setUp()
            throws Exception
    {

    }

    public static Test suite()
    {
        return new TestSuite(MethodKeyTestCase.class);
    }

    public void testEquals()
    {
        Class [] elements = new Class [] { Object.class };
        ASTMethod m1 = new ASTMethod(23);
        MethodCacheKey mck = m1.new MethodCacheKey(elements);

        assertTrue(mck.equals(mck));
        assertTrue(!mck.equals(null));
        assertTrue(!mck.equals((MethodCacheKey) null));
    }

    public void testEqualsPathological()
    {
        Class [] elements = new Class [] {};
        ASTMethod m1 = new ASTMethod(23);
        MethodCacheKey mck = m1.new MethodCacheKey(elements);

        assertTrue(mck.equals(mck));
        assertTrue(!mck.equals(null));
        assertTrue(!mck.equals((MethodCacheKey) null));
    }

    public void testEqualsPathological2()
    {
        Class [] elements = new Class [] {null};
        ASTMethod m1 = new ASTMethod(23);
        MethodCacheKey mck = m1.new MethodCacheKey(elements);

        assertTrue(mck.equals(mck));
        assertTrue(!mck.equals(null));
        assertTrue(!mck.equals((MethodCacheKey) null));
    }

    public void testEqualsBad()
    {
        Class [] elements = ArrayUtils.EMPTY_CLASS_ARRAY;
        ASTMethod m1 = new ASTMethod(23);
        MethodCacheKey mck = m1.new MethodCacheKey(elements);

        assertTrue(mck.equals(mck));
        assertTrue(!mck.equals(null));
        assertTrue(!mck.equals((MethodCacheKey) null));
    }

    public void testEqualsWorse()
    {
        Class [] elements = null;
        ASTMethod m1 = new ASTMethod(23);
        MethodCacheKey mck = m1.new MethodCacheKey(elements);

        assertTrue(mck.equals(mck));
        assertTrue(!mck.equals(null));
        assertTrue(!mck.equals((MethodCacheKey) null));
    }

    public void testHashCode()
    {
        Class [] e1 = new Class [] { String.class, String.class };
        Class [] e2 = new Class [] { Object.class, Object.class };

        ASTMethod m1 = new ASTMethod(23);
        MethodCacheKey mck1 = m1.new MethodCacheKey(e1);
        MethodCacheKey mck2 = m1.new MethodCacheKey(e2);

        assertTrue(mck1.hashCode() != mck2.hashCode());
    }
}


--- cut ---

I'd like the code to pass all of those tests. Yes, there is a number
of pathologic tests.

To make a medium length story short: That code would not pass my
personal QA. I'd like to get it reviewed and fixed. I will not insist
on it (that's why it is -0.5 and not -1 but it will make me sad if the
code stays as it is).


        Best regards
                Henning

--
Dipl.-Inf. (Univ.) Henning P. Schmiedehausen          INTERMETA GmbH
[EMAIL PROTECTED]        +49 9131 50 654 0   http://www.intermeta.de/

RedHat Certified Engineer -- Jakarta Turbine Development  -- hero for hire
   Linux, Java, perl, Solaris -- Consulting, Training, Development

Social behaviour: Bavarians can be extremely egalitarian and folksy.
                                    -- http://en.wikipedia.org/wiki/Bavaria
Most Franconians do not like to be called Bavarians.
                                    -- http://en.wikipedia.org/wiki/Franconia

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




--
Forio Business Simulations

Will Glass-Husain
[EMAIL PROTECTED]
www.forio.com

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

Reply via email to