Why would an add() in a TreeSet subclass fail?

Discussion in 'Java' started by Rhino, Feb 22, 2005.

  1. Rhino

    Rhino Guest

    I have a weird problem with regards to an add() method in the constructor of
    a TreeSet subclass: the add() fails but I can't figure out why.

    I have a class called StringRange that subclasses TreeSet as follows:

    public class StringRange extends TreeSet {

    public StringRange(String firstString, String secondString) {

    super();

    boolean result = add(firstString);
    if (result != true) {
    throw new IllegalArgumentException("The first String was not
    added to the Range.");
    }
    result = add(secondString);
    if (result != true) {
    throw new IllegalArgumentException("The second String was not
    added to the Range.");
    }
    }

    }

    I instantiate StringRange like this:

    StringRange myRange = new StringRange("cat", "dog");

    and the size of the StringRange is correctly reported as two elements.


    Now, I'm trying to create a generalized Range class, like this:

    public class Range extends TreeSet {

    public Range(Object firstObject, Object secondObject) {

    super();

    boolean result = add(firstObject);
    if (result != true) {
    throw new IllegalArgumentException("The first Object was not
    added to the Range.");
    }
    result = add(secondObject);
    if (result != true) {
    throw new IllegalArgumentException("The second Object was not
    added to the Range.");
    }
    }

    }

    However, when I execute this code:

    Range myRange = new Range("cat", "dog");

    the reported size of the Range is 0, not 2. I traced through the code with
    my debugger and found that the add() method in the Range() constructor
    returns false; neither value is added to the Range.

    I'm baffled: *WHY* does add() fail? There is no feedback from Java beyond
    the 'false' returned by the add() method; the only difference I can see
    between StringRange and Range is that Range expects two Objects while
    StringRange expects two Strings in its constructor yet add() fails in the
    Range class while it works perfectly in StringRange.

    What's going on here?

    --
    Rhino
    ---
    rhino1 AT sympatico DOT ca
    "There are two ways of constructing a software design. One way is to make it
    so simple that there are obviously no deficiencies. And the other way is to
    make it so complicated that there are no obvious deficiencies." - C.A.R.
    Hoare
     
    Rhino, Feb 22, 2005
    #1
    1. Advertising

  2. Rhino

    Eric Sosman Guest

    Rhino wrote:
    > I have a weird problem with regards to an add() method in the constructor of
    > a TreeSet subclass: the add() fails but I can't figure out why.
    > [...]


    Neither can I: The sample code you provided works just
    fine for me, once I add the missing pieces. Perhaps the
    pieces I added aren't the same as those you're using? Try
    posting a minimal *complete* program that demonstrates the
    problem, and I'll take another whack at it.

    --
     
    Eric Sosman, Feb 22, 2005
    #2
    1. Advertising

  3. Rhino

    Steve Bosman Guest

    Rhino wrote:
    > I have a weird problem with regards to an add() method in the

    constructor of
    > a TreeSet subclass: the add() fails but I can't figure out why.
    >
    > I have a class called StringRange that subclasses TreeSet as follows:
    >

    [Snip StringRange class]
    >
    > I instantiate StringRange like this:
    >
    > StringRange myRange = new StringRange("cat", "dog");
    >
    > and the size of the StringRange is correctly reported as two

    elements.
    >
    >
    > Now, I'm trying to create a generalized Range class, like this:
    >

    [Snip Range class]
    > However, when I execute this code:
    >
    > Range myRange = new Range("cat", "dog");
    >
    > the reported size of the Range is 0, not 2. I traced through the code

    with
    > my debugger and found that the add() method in the Range()

    constructor
    > returns false; neither value is added to the Range.
    >
    > I'm baffled: *WHY* does add() fail? There is no feedback from Java

    beyond
    > the 'false' returned by the add() method; the only difference I can

    see
    > between StringRange and Range is that Range expects two Objects while
    > StringRange expects two Strings in its constructor yet add() fails in

    the
    > Range class while it works perfectly in StringRange.
    >
    > What's going on here?
    >

    As far as I can tell nothing, I pasted the two classes into classes in
    my IDE, created a main method and both test lines report a size of 2.
    Are you positive this is the same code that was failing?
     
    Steve Bosman, Feb 22, 2005
    #3
  4. Rhino,

    Do please consider the responses to your other thread about ranges.
    Chris Uppal in particular provided a nice, concise explanation for why
    it isn't a good idea to base your generic Range class on TreeSet, and he
    also raised some relevant issues concerning endpoints that you should
    consider. Other responses discuss a simple solution not based on
    TreeSet; although it uses generics ala Java 1.5, it can easily be
    modified to be compatible with earlier versions of Java.

    --
    John Bollinger
     
    John C. Bollinger, Feb 22, 2005
    #4
  5. Rhino

    Rhino Guest

    "Eric Sosman" <> wrote in message
    news:cvfhmo$jif$...
    >
    >
    > Rhino wrote:
    > > I have a weird problem with regards to an add() method in the

    constructor of
    > > a TreeSet subclass: the add() fails but I can't figure out why.
    > > [...]

    >
    > Neither can I: The sample code you provided works just
    > fine for me, once I add the missing pieces. Perhaps the
    > pieces I added aren't the same as those you're using? Try
    > posting a minimal *complete* program that demonstrates the
    > problem, and I'll take another whack at it.
    >

    As requested, here's a complete example. It compiles fine in my IDE but the
    add() fails in both constructors, throwing the IllegalArgumentException; if
    I omit the try blocks, the adds invariably return false and proceed
    silently.


    import java.util.Collection;
    import java.util.TreeSet;

    /**
    * Class Range describes the behaviour of all ranges, regardless of the
    class of
    * data stored in it.
    *
    * <p>
    * A Range is a special TreeSet containing exactly two elements, the first
    of
    * which represents the low end of the range and the second of which
    represents
    * the high end of the range.
    * </p>
    *
    */
    public class Range extends TreeSet {

    public static void main(String[] args) {

    Range myRange = new Range("cat", "dog");
    System.out.println("Size: " + myRange.size());
    }

    /**
    * Constructor Range(Object, Object) creates an instance of the class.
    *
    * <p>
    * Additional comments about constructor Range.
    * </p>
    *
    * @param Object
    * firstObject one of the two Object values that will
    encompass
    * the range
    * @param Object
    * secondObject one of the two Object values that will
    encompass
    * the range
    */
    public Range(Object firstObject, Object secondObject) {

    super();

    System.out.println("firstObject: " + firstObject);
    System.out.println("secondObject: " + secondObject);

    /* Verify that both objects are the same class. */
    String firstObjectClass = firstObject.getClass().getName();
    String secondObjectClass = secondObject.getClass().getName();
    if (!firstObjectClass.equals(secondObjectClass)) {
    throw new IllegalArgumentException("The two Objects passed to
    this constructor must be the same class.");
    }

    /* Add the two objects to the Range. Make sure both adds succeed. */
    boolean result = add((String) firstObject);
    if (result != true) {
    throw new IllegalArgumentException("The first Object was not
    added to the Range.");
    }

    result = add((String) secondObject);
    if (result != true) {
    throw new IllegalArgumentException("The second Object was not
    added to the Range.");
    }

    System.out.println("Range(Object, Object) size: " + size());
    }

    /**
    * Constructor Range(Object[]) creates an instance of the class.
    *
    * @param Object[]
    * range an array of objects
    */
    public Range(Object[] range) {

    super();

    System.out.println("range.length: " + range.length);
    System.out.println("range[0]: " + range[0]);
    System.out.println("range[1]: " + range[1]);

    if (range.length != 2) {
    throw new IllegalArgumentException(
    "The range array must contain exactly two elements.");
    }

    /* Verify that both objects are the same class. */
    String firstObjectClass = range[0].getClass().getName();
    String secondObjectClass = range[1].getClass().getName();
    if (!firstObjectClass.equals(secondObjectClass)) {
    throw new IllegalArgumentException("The two Objects passed to
    this constructor must be the same class.");
    }

    /* Add the two objects to the Range. Make sure both adds succeed. */
    boolean result = add(range[0]);
    if (result != true) {
    throw new IllegalArgumentException("The first Object was not
    added to the Range.");
    }

    result = add(range[1]);
    if (result != true) {
    throw new IllegalArgumentException("The second Object was not
    added to the Range.");
    }

    System.out.println("Range(Object[]) size: " + size());
    }

    /**
    * This overridden version of the add() method ensures that no elements
    are ever added to
    * the range after it has been instantiated.
    *
    * @return false
    */
    public boolean add(Object object) {

    return false;
    }

    /**
    * This overridden version of the addAll() method ensures that no
    elements are ever added to
    * the range after it has been instantiated.
    *
    * @return false
    */
    public boolean addAll(Collection collection) {

    return false;
    }

    /**
    * This overridden version of the remove() method ensures that no
    elements are ever
    * removed from the range after it has been instantiated.
    *
    * @return false
    */
    public boolean remove(Object object) {

    return false;
    }

    /**
    * This method gets the values that comprise the range. There will only
    ever be two values.
    *
    * @return Object array containing the low and high values in the range,
    respectively
    */
    public Object[] getValues() {

    Object[] values = new Object[size()];

    values[0] = first();
    values[1] = last();

    return values;
    }

    }
     
    Rhino, Feb 22, 2005
    #5
  6. Rhino

    Rhino Guest

    "John C. Bollinger" <> wrote in message
    news:cvfli5$ni2$...
    > Rhino,
    >
    > Do please consider the responses to your other thread about ranges.
    > Chris Uppal in particular provided a nice, concise explanation for why
    > it isn't a good idea to base your generic Range class on TreeSet, and he
    > also raised some relevant issues concerning endpoints that you should
    > consider. Other responses discuss a simple solution not based on
    > TreeSet; although it uses generics ala Java 1.5, it can easily be
    > modified to be compatible with earlier versions of Java.
    >

    Thanks, I've just seen his note - which wasn't there yet when I started this
    thread - and am strongly considering his remarks. I will almost certainly
    redesign my Range class NOT to be a subclass of TreeSet.

    However, I'm still stumped by why my constructors aren't adding the values
    passed to them and I'd like to figure that out even if I don't subclass
    TreeSet in this particular case. Any ideas?

    I've just posted a complete version of the class which demonstrates the
    problem in reply to Eric Sosman's response to my question. The code is so
    SIMPLE that I can't see why it doesn't work; it's either a really basic
    coding mistake or something that is going to teach me something important
    about Java. In the latter case, I expect to learn something useful about
    Java ;-)

    Rhino
     
    Rhino, Feb 22, 2005
    #6
  7. Rhino

    Eric Sosman Guest

    Rhino wrote:
    > "Eric Sosman" <> wrote in message
    > news:cvfhmo$jif$...
    >
    >>
    >>Rhino wrote:
    >>
    >>>I have a weird problem with regards to an add() method in the
    >>> constructor of
    >>>a TreeSet subclass: the add() fails but I can't figure out why.
    >>>[...]

    >
    > As requested, here's a complete example. It compiles fine in my IDE but the
    > add() fails in both constructors, [...]


    Could it be because of the following? (Just a hunch ;-)

    > /**
    > * This overridden version of the add() method ensures that no elements
    > are ever added to
    > * the range after it has been instantiated.
    > *
    > * @return false
    > */
    > public boolean add(Object object) {
    >
    > return false;
    > }


    --
     
    Eric Sosman, Feb 22, 2005
    #7
  8. Rhino

    Guest

    > As requested, here's a complete example. It compiles fine in my IDE but the
    > add() fails in both constructors, throwing the IllegalArgumentException; if
    > I omit the try blocks, the adds invariably return false and proceed
    > silently.


    The code does exactly what it should. You return false in your method.
    So, congratulation, you haven't gone mad yet.

    [...]

    > public class Range extends TreeSet {
    >
    > public static void main(String[] args) {
    >
    > Range myRange = new Range("cat", "dog");


    [...]

    > boolean result = add((String) firstObject);
    > if (result != true) {
    > throw new IllegalArgumentException("The first Object was not
    > added to the Range.");
    > }


    [...]

    > public boolean add(Object object) {
    >
    > return false;


    Do you want to do this?

    > }


    Maybe some sleep helps ;). Best regards,
    Roberto Nibali, ratz
    --
    echo
    '[q]sa[ln0=aln256%Pln256/snlbx]sb3135071790101768542287578439snlbxq' | dc
     
    , Feb 22, 2005
    #8
  9. Rhino

    Rhino Guest

    <> wrote in message
    news:421b6ced$...
    > > As requested, here's a complete example. It compiles fine in my IDE but

    the
    > > add() fails in both constructors, throwing the IllegalArgumentException;

    if
    > > I omit the try blocks, the adds invariably return false and proceed
    > > silently.

    >
    > The code does exactly what it should. You return false in your method.
    > So, congratulation, you haven't gone mad yet.
    >


    Are you saying that the add() SHOULD fail or are you just congratulating me
    on detecting the failure cleanly and reporting it to the user?


    Do you have any idea why the add() fails or what I need to do to make it
    work?


    > [...]
    >
    > > public class Range extends TreeSet {
    > >
    > > public static void main(String[] args) {
    > >
    > > Range myRange = new Range("cat", "dog");

    >
    > [...]
    >
    > > boolean result = add((String) firstObject);
    > > if (result != true) {
    > > throw new IllegalArgumentException("The first Object was not
    > > added to the Range.");
    > > }

    >
    > [...]
    >
    > > public boolean add(Object object) {
    > >
    > > return false;

    >
    > Do you want to do this?
    >

    That's another issue that I'd like to leave aside for now; I'll get back to
    that later.

    > > }

    >
    > Maybe some sleep helps ;). Best regards,
    > Roberto Nibali, ratz
    > --
    > echo
    > '[q]sa[ln0=aln256%Pln256/snlbx]sb3135071790101768542287578439snlbxq' | dc
     
    Rhino, Feb 22, 2005
    #9
  10. Rhino

    Guest

    >>The code does exactly what it should. You return false in your method.
    >>So, congratulation, you haven't gone mad yet.
    >>

    >
    > Are you saying that the add() SHOULD fail


    Yes, since that's what you wrote in your code.

    > or are you just congratulating me
    > on detecting the failure cleanly and reporting it to the user?


    Neither ;).

    > Do you have any idea why the add() fails or what I need to do to make it
    > work?


    I tend to say yes ... but you're actually starting to confusing me.
    Let's see you call path:

    Is it ok if I add following line to your code?

    public boolean add(Object object) {
    ----> System.out.println("I will return false here");
    return false;
    }

    Let's run your code with my line in it:

    ratz@webphish:~> javac Range.java
    ratz@webphish:~> java Range
    firstObject: cat
    secondObject: dog
    I will return false here
    Exception in thread "main" java.lang.IllegalArgumentException: The first
    Object was not added to the Range.
    at Range.<init>(Range.java:20)
    at Range.main(Range.java:6)
    ratz@webphish:~>

    I have taken out all comments and unneccessary empty lines of your code:

    import java.util.Collection;
    import java.util.TreeSet;

    public class Range extends TreeSet {
    public static void main(String[] args) {
    Range myRange = new Range("cat", "dog");

    # here you try to get a new instance of your class Range, let's
    # skip to your constructor ...

    System.out.println("Size: " + myRange.size());
    }
    public Range(Object firstObject, Object secondObject) {

    # so we're here now during the instantiation of the class ...

    super();
    System.out.println("firstObject: " + firstObject);
    System.out.println("secondObject: " + secondObject);

    # this actually also gets printed as you can see in my test run

    String firstObjectClass = firstObject.getClass().getName();
    String secondObjectClass = secondObject.getClass().getName();
    if (!firstObjectClass.equals(secondObjectClass)) {
    throw new IllegalArgumentException("The two Objects passed
    to this c
    onstructor must be the same class.");
    }

    # nothing to see here, so let's proceed quickly

    boolean result = add((String) firstObject);

    # so we call our add() method, let's jump to the code location ...

    if (result != true) {

    # since the result variable will be false this statement is true and
    # an Exception is thrown. End of program and of story.

    throw new IllegalArgumentException("The first Object was
    not added t
    o the Range.");
    }
    result = add((String) secondObject);
    if (result != true) {
    throw new IllegalArgumentException("The second Object was
    not added
    to the Range.");
    }
    System.out.println("Range(Object, Object) size: " + size());
    }
    public Range(Object[] range) {
    super();
    System.out.println("range.length: " + range.length);
    System.out.println("range[0]: " + range[0]);
    System.out.println("range[1]: " + range[1]);
    if (range.length != 2) {
    throw new IllegalArgumentException( "The range array must
    contain ex
    actly two elements.");
    }
    String firstObjectClass = range[0].getClass().getName();
    String secondObjectClass = range[1].getClass().getName();
    if (!firstObjectClass.equals(secondObjectClass)) {
    throw new IllegalArgumentException("The two Objects passed
    to this c
    onstructor must be the same class.");
    }
    boolean result = add(range[0]);
    if (result != true) {
    throw new IllegalArgumentException("The second Object was
    not added
    to the Range.");
    }
    System.out.println("Range(Object[]) size: " + size());
    }
    public boolean add(Object object) {

    # this is the infamous add method

    System.out.println("I will return false here");

    # this will be printed as shown in my test run

    return false;

    # and the only statement that you have here is to return false!!!
    # let's jump back to the caller code fragment ...

    }
    public boolean addAll(Collection collection) {
    return false;
    }
    public boolean remove(Object object) {
    return false;
    }
    public Object[] getValues() {
    Object[] values = new Object[size()];
    values[0] = first();
    values[1] = last();
    return values;
    }
    }

    HTH and best regards,
    Roberto Nibali, ratz
    --
    echo
    '[q]sa[ln0=aln256%Pln256/snlbx]sb3135071790101768542287578439snlbxq' | dc
     
    , Feb 22, 2005
    #10
  11. Rhino

    Eric Sosman Guest

    Rhino wrote:
    > <> wrote in message
    > news:421b6ced$...
    >
    >>>As requested, here's a complete example. It compiles fine in my IDE but

    >
    > the
    >
    >>>add() fails in both constructors, throwing the IllegalArgumentException;

    >
    > if
    >
    >>>I omit the try blocks, the adds invariably return false and proceed
    >>>silently.

    >>
    >>The code does exactly what it should. You return false in your method.
    >>So, congratulation, you haven't gone mad yet.
    >>

    >
    > Are you saying that the add() SHOULD fail or are you just congratulating me
    > on detecting the failure cleanly and reporting it to the user?
    >
    > Do you have any idea why the add() fails or what I need to do to make it
    > work?


    What you seem to be missing is that it's *your* add()
    method that fails. You overrode TreeSet's add() with your
    own, whose entire body is `return false;' -- so it returns
    `false', just as you told it to. Your code never calls
    TreeSet's add() method at all.

    --
     
    Eric Sosman, Feb 22, 2005
    #11
  12. Rhino

    Rhino Guest

    "Eric Sosman" <> wrote in message
    news:cvfuei$pgv$...
    >
    >
    > Rhino wrote:
    > > <> wrote in message
    > > news:421b6ced$...
    > >
    > >>>As requested, here's a complete example. It compiles fine in my IDE but

    > >
    > > the
    > >
    > >>>add() fails in both constructors, throwing the

    IllegalArgumentException;
    > >
    > > if
    > >
    > >>>I omit the try blocks, the adds invariably return false and proceed
    > >>>silently.
    > >>
    > >>The code does exactly what it should. You return false in your method.
    > >>So, congratulation, you haven't gone mad yet.
    > >>

    > >
    > > Are you saying that the add() SHOULD fail or are you just congratulating

    me
    > > on detecting the failure cleanly and reporting it to the user?
    > >
    > > Do you have any idea why the add() fails or what I need to do to make it
    > > work?

    >
    > What you seem to be missing is that it's *your* add()
    > method that fails. You overrode TreeSet's add() with your
    > own, whose entire body is `return false;' -- so it returns
    > `false', just as you told it to. Your code never calls
    > TreeSet's add() method at all.
    >

    As Homer Simpson would say if he were a Java coder, D'OH! Wow, I can't
    believe I missed that! Talk about shooting yourself in the foot ;-)

    Okay, the failure of the add() now makes perfect sense.

    It raises another question though. What is the best way to ensure that my
    Range class never gets any new members? I want to give it two values in the
    constructor - either two discrete values or an array containing two discrete
    values - and then I never want anyone to add new values or remove existing
    values.

    That's why I overrode the add() method to always return false(); I didn't
    want to let it add additional values beyond the two supplied via the
    constructor.

    Rhino
     
    Rhino, Feb 22, 2005
    #12
  13. Rhino

    Eric Sosman Guest

    Rhino wrote:
    > [...]
    > It raises another question though. What is the best way to ensure that my
    > Range class never gets any new members? I want to give it two values in the
    > constructor - either two discrete values or an array containing two discrete
    > values - and then I never want anyone to add new values or remove existing
    > values.
    >
    > That's why I overrode the add() method to always return false(); I didn't
    > want to let it add additional values beyond the two supplied via the
    > constructor.


    The bad way would be to override add() and addAll() as
    you do now, but to invoke super.add() instead of plain add()
    in the constructor.

    A better way would be to use composition instead of
    inheritance. Don't have Range extend TreeSet; have it
    contain a TreeSet instance as a private member variable:

    public class Range {
    private TreeSet set;

    public Range(Object u, Object v) {
    set = new TreeSet();
    set.add(u);
    set.add(v);
    }

    /* ... other fields and methods ... */
    }

    Points to ponder:

    - If the "other fields and methods" don't absolutely
    require a TreeSet, `private Set set;' would be better.

    - You could make Range implement Set by adding methods
    that forwarded to the contained TreeSet.

    - You might learn a good deal from reading "Effective
    Java" by Joshua Bloch. In particular, Bloch explains
    why the first approach suggested above is "bad" and
    why the second is "better."

    - Why in the ever-lovin' blue-eyed mornin' do you want
    to drag out the entire machinery of a TreeSet just to
    keep track of two count them two objects? Canaricide
    by cannon, that's what it is.

    --
     
    Eric Sosman, Feb 22, 2005
    #13
  14. Rhino

    Rhino Guest

    "Eric Sosman" <> wrote in message
    news:cvg3ac$rvr$...
    >
    >
    > Rhino wrote:
    > > [...]
    > > It raises another question though. What is the best way to ensure that

    my
    > > Range class never gets any new members? I want to give it two values in

    the
    > > constructor - either two discrete values or an array containing two

    discrete
    > > values - and then I never want anyone to add new values or remove

    existing
    > > values.
    > >
    > > That's why I overrode the add() method to always return false(); I

    didn't
    > > want to let it add additional values beyond the two supplied via the
    > > constructor.

    >
    > The bad way would be to override add() and addAll() as
    > you do now, but to invoke super.add() instead of plain add()
    > in the constructor.
    >

    This is the current version of my Range.add() method; it works fine, even if
    the design is bad:

    public boolean add(Object object) {

    if (size() >= 2) {
    return false;
    } else {
    return super.add(object);
    }
    }

    I'm not quite clear why this is a bad design. It certainly feels kludgey to
    have code like 'if (size() >= 2)' but what are the risks and dangers of
    leaving this as it is?

    > A better way would be to use composition instead of
    > inheritance. Don't have Range extend TreeSet; have it
    > contain a TreeSet instance as a private member variable:
    >
    > public class Range {
    > private TreeSet set;
    >
    > public Range(Object u, Object v) {
    > set = new TreeSet();
    > set.add(u);
    > set.add(v);
    > }
    >
    > /* ... other fields and methods ... */
    > }
    >
    > Points to ponder:
    >
    > - If the "other fields and methods" don't absolutely
    > require a TreeSet, `private Set set;' would be better.
    >
    > - You could make Range implement Set by adding methods
    > that forwarded to the contained TreeSet.
    >

    I'm a little confused: if it is a bad idea to subclass TreeSet for the
    limited functionality that I need for reasons of overkill, doesn't the use
    of a TreeSet as a private variable raise the same concerns? Either way, I'm
    still implementing a TreeSet with all of its overhead when a simpler class
    would probably do the job....

    > - You might learn a good deal from reading "Effective
    > Java" by Joshua Bloch. In particular, Bloch explains
    > why the first approach suggested above is "bad" and
    > why the second is "better."
    >

    I'm certainly not opposed to learning how to design better in Java. As I
    said at the start of the other thread, my OO design background isn't
    particularly strong. First, I haven't had any formal training in OO (for a
    variety of reasons). Second, I haven't found a really good explanation of OO
    design yet. The attempts I've seen all seem to talk only in Big Concepts
    without giving concrete examples that help clarify *exactly* what is meant
    by the terms they are using. Without a clear understanding of those first
    pages, it feels pointless to go deeper into these explanations so I tend to
    move on and do something else.

    I'll put Bloch's book on my list of books to investigate and buy a copy if
    it communicates better than the other books I've seen on OO design.

    If you are saying my current design is bad - and I am perfectly willing to
    agree that it probably is! - and Bloch's design is better, does that mean
    there is a 'best' out there, i.e. a solution that is even better than
    Bloch's? If so, how would I find out what that solution is and HOW IT WORKS?
    (I won't learn much if I see a solution that is supposed to be better unless
    it comes with an explanation of how it works and why it's better.)

    > - Why in the ever-lovin' blue-eyed mornin' do you want
    > to drag out the entire machinery of a TreeSet just to
    > keep track of two count them two objects? Canaricide
    > by cannon, that's what it is.
    >

    You raise a perfectly valid point and one that I have been mulling over
    since someone else mentioned it on the other thread I launched to discuss
    the design of my Range class.

    I'm not sure this is a _GOOD_ answer but the truth is that I was trying to
    imitate the SQL operators IN and BETWEEN. I don't know if you are familiar
    with SQL so forgive me if I elaborate on that just a tiny bit.

    In SQL, the IN operator gets used like this:

    select * from employee
    where empno in (1, 2, 5, 6, 8)

    The BETWEEN operator gets used like this:

    select * from employee
    where hiredate between '1990-01-01' and '1999-12-31'

    A key feature of both operators is that they are overloaded: IN and BETWEEN
    both let you compare a value to either a set or a range of any of the SQL
    datatypes as long as the search value and the values in the set or range are
    comparable. Therefore, you can do an IN or BETWEEN on Strings, ints, dates,
    or whatever.

    Since the IN operator can have a (virtually) unlimited number of values in
    its argument list, I decided that the Java equivalent of that argument list
    would be a Collection, specifically a TreeSet since the algorithm to
    determine if a given value was in the Collection seemed likely to be more
    efficient if the Collection values were in order. I developed a class
    containing static methods that did a variety of INs.

    Having decided that the set required by an IN was suited to a TreeSet, it
    seemed a logical extension to store the range implied by the BETWEEN
    operator in a specialized Collection, specifically a TreeSet containing two
    values (where the low value was always first). So, I started developing
    specific Range classes, like StringRange and IntRange to handle String and
    int ranges respectively. Then I thought I should generalize the solution so
    I came up with the concept of the Range class. When I got into trouble
    making Range work, I started this thread.

    Now that I've fixed the problem with the add() method, this approach seems
    to be working and gives me the behaviour I want - at least as far as I've
    tested.

    On the other hand, this solution is probably massive overkill compared to
    what I actually need to properly store ranges so that I can do BETWEENs.

    Someone on the other thread suggested doing something a lot simpler so I've
    been thinking along the lines of something more like the Point class and
    have started work on this simpler Range2 class (I'm calling it Range2 to
    distinguish it from Range; if I finally decide to delete Range entirely,
    I'll rename Range2 to Range.)

    Rhino
     
    Rhino, Feb 22, 2005
    #14
  15. Rhino

    Chris Uppal Guest

    Rhino wrote:

    > I'm certainly not opposed to learning how to design better in Java. As I
    > said at the start of the other thread, my OO design background isn't
    > particularly strong. First, I haven't had any formal training in OO (for a
    > variety of reasons). Second, I haven't found a really good explanation of
    > OO design yet. The attempts I've seen all seem to talk only in Big
    > Concepts without giving concrete examples that help clarify *exactly*
    > what is meant by the terms they are using.


    There are /masses/ of bad books that claim to teach OO. They fall into at
    least two camps: one camp confuses "OO" with "the syntax/semantics of
    Java/C++/<whatever>" (I assume their authors don't actually understand OO at
    all [*]); the other camp goes soaring off into the stratosphere and apparently
    forget that OO is just a way to make programming abstractions more amenable to
    the intuitions about people (and things, but mostly people) that have been
    hard-wired into our brains by millenia of evolution -- i.e. its the very
    /opposite/ of abstract!

    ([*] though they may be good books on the syntax/semantics of
    Java/C++/<whatever> in their own right, that doesn't in itself make them good
    books to learn OO from.)

    FWIW, the best book I know of on this subject is:

    Object Design
    Roles, Responsibilities, and Collaborations
    Rebacca Wirfs-Brock and Alan McKean

    That's not a primer -- it assumes that you are interested enough in the subject
    to be willing to read a /whole book/ about it (and most programmers would not
    be). It's also language neutral (so there's no code in it). OTOH it is
    entirely free from UML (I tend to see UML in books as a symptom of falling into
    one or other of the camps I mentioned earlier -- though a little bit of it is
    OK). It's pretty concrete in that it uses lots of examples and avoids the
    theoretical verbiage. But it still is /fairly/ abstract, though, so don't
    spend money on it without a chance to browse through it first.

    -- chris
     
    Chris Uppal, Feb 23, 2005
    #15
  16. Rhino

    Rhino Guest

    "Chris Uppal" <-THIS.org> wrote in message
    news:421c751e$1$38042$...
    > Rhino wrote:
    >
    > > I'm certainly not opposed to learning how to design better in Java. As I
    > > said at the start of the other thread, my OO design background isn't
    > > particularly strong. First, I haven't had any formal training in OO (for

    a
    > > variety of reasons). Second, I haven't found a really good explanation

    of
    > > OO design yet. The attempts I've seen all seem to talk only in Big
    > > Concepts without giving concrete examples that help clarify *exactly*
    > > what is meant by the terms they are using.

    >
    > There are /masses/ of bad books that claim to teach OO. They fall into at
    > least two camps: one camp confuses "OO" with "the syntax/semantics of
    > Java/C++/<whatever>" (I assume their authors don't actually understand OO

    at
    > all [*]); the other camp goes soaring off into the stratosphere and

    apparently
    > forget that OO is just a way to make programming abstractions more

    amenable to
    > the intuitions about people (and things, but mostly people) that have been
    > hard-wired into our brains by millenia of evolution -- i.e. its the very
    > /opposite/ of abstract!
    >
    > ([*] though they may be good books on the syntax/semantics of
    > Java/C++/<whatever> in their own right, that doesn't in itself make them

    good
    > books to learn OO from.)
    >

    I'm glad to hear it isn't only me that has had these experiences; I was
    beginning to think I just wasn't very bright....

    > FWIW, the best book I know of on this subject is:
    >
    > Object Design
    > Roles, Responsibilities, and Collaborations
    > Rebacca Wirfs-Brock and Alan McKean
    >
    > That's not a primer -- it assumes that you are interested enough in the

    subject
    > to be willing to read a /whole book/ about it (and most programmers would

    not
    > be). It's also language neutral (so there's no code in it). OTOH it is
    > entirely free from UML (I tend to see UML in books as a symptom of falling

    into
    > one or other of the camps I mentioned earlier -- though a little bit of it

    is
    > OK). It's pretty concrete in that it uses lots of examples and avoids the
    > theoretical verbiage. But it still is /fairly/ abstract, though, so don't
    > spend money on it without a chance to browse through it first.
    >

    Thanks for the suggestion; I'll have a look for that book and see if it
    speaks to me as clearly as it does to you ;-)

    Rhino
     
    Rhino, Feb 23, 2005
    #16
  17. Rhino

    Eric Sosman Guest

    Rhino wrote:
    > [trying to extend TreeSet but ensure that two and only two
    > objects are ever inserted into it]
    >
    > This is the current version of my Range.add() method; it works fine, even if
    > the design is bad:
    >
    > public boolean add(Object object) {
    >
    > if (size() >= 2) {
    > return false;
    > } else {
    > return super.add(object);
    > }
    > }
    >
    > I'm not quite clear why this is a bad design. It certainly feels kludgey to
    > have code like 'if (size() >= 2)' but what are the risks and dangers of
    > leaving this as it is?


    .... and you've overridden addAll() as well, to make sure nobody
    can insert an unwanted object into your TreeSet that way, either.
    Fine, you've locked all the doors.

    .... except that TreeSet offers clear() and remove() methods
    that a client could use to delete one or both of the "two and
    only two" objects after your Range is constructed. Do you
    want to prevent that? Then get busy and write a few more
    overrides to lock those two doors. TreeSet also has iterator(),
    and a client could use the returned Iterator object to muck
    with your Range. Do you want to prevent that? More busy work.
    And what about the subSet(), tailSet(), and headSet() methods?
    Haul out your editor and write still more overrides ...

    Eventually, after many overrides, you'll have locked all
    the doors and made your Range unmodifiable. But wait! Acceding
    to enormous community pressure, Java 1.7 comes along and defines
    a new addFiltered(Collection, Filter) method to all the modifiable
    collections. A new door has suddenly opened in your fortress
    wall, and the Orcs are charging through it ... Your nightmare
    never ends.

    >> A better way would be to use composition instead of
    >>inheritance. Don't have Range extend TreeSet; have it
    >>contain a TreeSet instance as a private member variable:

    >
    > I'm a little confused: if it is a bad idea to subclass TreeSet for the
    > limited functionality that I need for reasons of overkill, doesn't the use
    > of a TreeSet as a private variable raise the same concerns? Either way, I'm
    > still implementing a TreeSet with all of its overhead when a simpler class
    > would probably do the job....


    If Range extends TreeSet, it automatically exposes all of
    TreeSet's behaviors and characteristics that you don't explicitly
    override. If Range instead *contains* a TreeSet instance, you
    can choose to expose only those characteristics you want. In
    particular, if the TreeSet instance is private within Range and
    if Range offers no method that lets a client get hold of the
    TreeSet reference, there is no way the client can call any methods
    on the TreeSet; only your Range class itself has the means to
    modify the TreeSet, and there's no way to do an end-run around
    your protective blockade.

    >> - Why in the ever-lovin' blue-eyed mornin' do you want
    >> to drag out the entire machinery of a TreeSet just to
    >> keep track of two count them two objects? Canaricide
    >> by cannon, that's what it is.
    >>

    > [trying to do something analogous to SQL's IN and BETWEEN]
    > Since the IN operator can have a (virtually) unlimited number of values in
    > its argument list, I decided that the Java equivalent of that argument list
    > would be a Collection, specifically a TreeSet since the algorithm to
    > determine if a given value was in the Collection seemed likely to be more
    > efficient if the Collection values were in order. I developed a class
    > containing static methods that did a variety of INs.
    >
    > Having decided that the set required by an IN was suited to a TreeSet, it
    > seemed a logical extension to store the range implied by the BETWEEN


    Here, I think, is where you go wrong. What's sauce for the
    goose is sauce for the gander, but tastes perfectly awful atop
    calamari. It is true you could model a range as the set of all
    elements within the range (back off, mathematicians: there are
    no non-denumerable sets on a finite computer), but that's not a
    great model. All you really care about are the endpoints of the
    range, and you'll use comparisons to decide whether a test item
    does or doesn't lie between them. Note, too, that a range requires
    an ordering notion while a set does not: you could speak of a set
    of points on the plane, but you cannot speak of a range of points
    on the plane (mathematicians: anybody who mentions space-filling
    curves is out of, er, line).

    Since ranges and sets are different in so many ways, there
    seems little reason to base them on the same data structure.
    Start by deciding on the behaviors you want; then and only
    then start thinking about the data structures to support them.

    --
     
    Eric Sosman, Feb 23, 2005
    #17
  18. Rhino

    Rhino Guest

    "Eric Sosman" <> wrote in message
    news:cvic6c$2o5$...
    >
    >
    > Rhino wrote:
    > > [trying to extend TreeSet but ensure that two and only two
    > > objects are ever inserted into it]
    > >
    > > This is the current version of my Range.add() method; it works fine,

    even if
    > > the design is bad:
    > >
    > > public boolean add(Object object) {
    > >
    > > if (size() >= 2) {
    > > return false;
    > > } else {
    > > return super.add(object);
    > > }
    > > }
    > >
    > > I'm not quite clear why this is a bad design. It certainly feels kludgey

    to
    > > have code like 'if (size() >= 2)' but what are the risks and dangers of
    > > leaving this as it is?

    >
    > ... and you've overridden addAll() as well, to make sure nobody
    > can insert an unwanted object into your TreeSet that way, either.
    > Fine, you've locked all the doors.
    >
    > ... except that TreeSet offers clear() and remove() methods
    > that a client could use to delete one or both of the "two and
    > only two" objects after your Range is constructed. Do you
    > want to prevent that? Then get busy and write a few more
    > overrides to lock those two doors. TreeSet also has iterator(),
    > and a client could use the returned Iterator object to muck
    > with your Range. Do you want to prevent that? More busy work.
    > And what about the subSet(), tailSet(), and headSet() methods?
    > Haul out your editor and write still more overrides ...
    >
    > Eventually, after many overrides, you'll have locked all
    > the doors and made your Range unmodifiable. But wait! Acceding
    > to enormous community pressure, Java 1.7 comes along and defines
    > a new addFiltered(Collection, Filter) method to all the modifiable
    > collections. A new door has suddenly opened in your fortress
    > wall, and the Orcs are charging through it ... Your nightmare
    > never ends.
    >

    Ok, you've made your point :) I see the dangers now.

    > >> A better way would be to use composition instead of
    > >>inheritance. Don't have Range extend TreeSet; have it
    > >>contain a TreeSet instance as a private member variable:

    > >
    > > I'm a little confused: if it is a bad idea to subclass TreeSet for the
    > > limited functionality that I need for reasons of overkill, doesn't the

    use
    > > of a TreeSet as a private variable raise the same concerns? Either way,

    I'm
    > > still implementing a TreeSet with all of its overhead when a simpler

    class
    > > would probably do the job....

    >
    > If Range extends TreeSet, it automatically exposes all of
    > TreeSet's behaviors and characteristics that you don't explicitly
    > override. If Range instead *contains* a TreeSet instance, you
    > can choose to expose only those characteristics you want. In
    > particular, if the TreeSet instance is private within Range and
    > if Range offers no method that lets a client get hold of the
    > TreeSet reference, there is no way the client can call any methods
    > on the TreeSet; only your Range class itself has the means to
    > modify the TreeSet, and there's no way to do an end-run around
    > your protective blockade.
    >

    Excellent! That answers my question very clearly. That makes a great deal of
    sense.

    For some reason, I got it into my head that using a TreeSet as my foundation
    was bad at least partially because it had so much overhead so that using
    composition reduced that overhead somehow; I was trying to figure out how
    that would work. Now I see that the real benefit is that you get far better
    security/stability since the TreeSet is totally internal to the Range.

    > >> - Why in the ever-lovin' blue-eyed mornin' do you want
    > >> to drag out the entire machinery of a TreeSet just to
    > >> keep track of two count them two objects? Canaricide
    > >> by cannon, that's what it is.
    > >>

    > > [trying to do something analogous to SQL's IN and BETWEEN]
    > > Since the IN operator can have a (virtually) unlimited number of values

    in
    > > its argument list, I decided that the Java equivalent of that argument

    list
    > > would be a Collection, specifically a TreeSet since the algorithm to
    > > determine if a given value was in the Collection seemed likely to be

    more
    > > efficient if the Collection values were in order. I developed a class
    > > containing static methods that did a variety of INs.
    > >
    > > Having decided that the set required by an IN was suited to a TreeSet,

    it
    > > seemed a logical extension to store the range implied by the BETWEEN

    >
    > Here, I think, is where you go wrong. What's sauce for the
    > goose is sauce for the gander, but tastes perfectly awful atop
    > calamari. It is true you could model a range as the set of all
    > elements within the range (back off, mathematicians: there are
    > no non-denumerable sets on a finite computer), but that's not a
    > great model. All you really care about are the endpoints of the
    > range, and you'll use comparisons to decide whether a test item
    > does or doesn't lie between them. Note, too, that a range requires
    > an ordering notion while a set does not: you could speak of a set
    > of points on the plane, but you cannot speak of a range of points
    > on the plane (mathematicians: anybody who mentions space-filling
    > curves is out of, er, line).
    >
    > Since ranges and sets are different in so many ways, there
    > seems little reason to base them on the same data structure.
    > Start by deciding on the behaviors you want; then and only
    > then start thinking about the data structures to support them.
    >

    Fair enough; as I said, I didn't really think it through very thoroughly
    before going with a TreeSet: I just got the notion that a Range was a very
    specialized set and therefore merited being a TreeSet just like the IN sets.
    I didn't realize all the consequences that you have pointed out so well.

    For what it's worth, I now have a SimpleRange class, which subclasses
    Object, that does all of what I need it to do to meet my needs without
    carrying around all the baggage of the TreeSet. Just for the heck of it,
    here it is. If you can see any problems or omissions in it, I'd be delighted
    to hear about it. I'm not sure I've covered everything we've talked about
    but I tried. I've also used JUnit to test everything; I probably haven't
    thrown as many exotic cases at it as I should but every test I have given it
    passed. By the way, I initially assumed that my Range or SimpleRange class
    would have to be further subclassed to handle specific datatypes but I'm
    starting to think that won't be necessary; thanks to polymorphism, this baby
    might just do the whole job ;-)


    /**
    * Class SimpleRange describes the behaviour of all ranges, regardless of
    the class of
    * data stored in it.
    *
    * <p>A SimpleRange is a class containing exactly two elements of the same
    type. These
    * elements represent the end-points of a range of values, e.g. 18 to 21 or
    A to E.</p>
    *
    * @author Rhino
    *
    */
    public class SimpleRange {

    public static int NUMBER_OF_ELEMENTS = 2;

    Comparable lowValue = null;
    Comparable highValue = null;

    /**
    * Constructor SimpleRange(Comparable, Comparable) creates an instance
    of the class.
    *
    * @param Comparable
    * firstObject one of the two Object values that will
    encompass
    * the range
    * @param Comparable
    * secondObject the other of the two Object values that will
    encompass
    * the range
    */
    public SimpleRange(Comparable firstObject, Comparable secondObject) {

    /* Verify that neither object is null. */
    if (firstObject == null) {
    throw new IllegalArgumentException("The first Object cannot be
    null.");
    }

    if (secondObject == null) {
    throw new IllegalArgumentException("The second Object cannot be
    null.");
    }

    /*
    * Since only Comparable objects are permitted by this constructor,
    compare them
    * and store the lower one in the lowValue class variable. Store the
    other one in the
    * highValue class variable.
    */
    try {
    if (firstObject.compareTo(secondObject) > 0) {
    highValue = firstObject;
    lowValue = secondObject;
    } else {
    lowValue = firstObject;
    highValue = secondObject;
    }
    } catch (ClassCastException cc_excp) {
    throw new IllegalArgumentException("The first Object, " +
    firstObject.toString() + ", belongs to class " +
    firstObject.getClass().getName() + ". The second Object, " +
    secondObject.toString() + ", belongs to class " +
    secondObject.getClass().getName() + ". Both Objects implement the Comparable
    interface but they cannot be compared with each other.");
    }

    }

    /**
    * Constructor SimpleRange(Comparable[]) creates an instance of the
    class.
    *
    * @param Object[]
    * range an array of Comparables
    */
    public SimpleRange(Comparable[] range) {

    /* Verify that the array is not null. */
    if (range == null) {
    throw new IllegalArgumentException("The range array cannot be
    null.");
    }

    /* Verify that the array contains exactly two elements. */
    if (range.length != 2) {
    throw new IllegalArgumentException(
    "The range array must contain exactly two elements.");
    }

    /* Verify that the array elements aren't null. */
    if (range[0] == null) {
    throw new IllegalArgumentException("The first array element
    cannot be null.");
    }

    if (range[1] == null) {
    throw new IllegalArgumentException("The second array element
    cannot be null.");
    }

    /*
    * Since only Comparable objects are permitted by this constructor,
    compare them
    * and store the lower one in the lowValue class variable. Store the
    other one in the
    * highValue class variable.
    */
    try {
    if (range[0].compareTo(range[1]) > 0) {
    lowValue = range[1];
    highValue = range[0];
    } else {
    lowValue = range[0];
    highValue = range[1];
    }
    } catch (ClassCastException cc_excp) {
    throw new IllegalArgumentException("The first Object, " +
    range[0].toString() + ", belongs to class " + range[0].getClass().getName()
    + ". The second Object, " + range[1].toString() + ", belongs to class " +
    range[1].getClass().getName() + ". Both Objects implement the Comparable
    interface but they cannot be compared with each other.");
    }

    }

    /**
    * Method size() returns the size of this SimpleRange; the value will
    always be 2.
    *
    * @return the size of this SimpleRange (always 2)
    */
    public int size() {

    return NUMBER_OF_ELEMENTS;
    }

    /**
    * Method getValues() returns all of the values in this SimpleRange.
    *
    * @return all of the values in this SimpleRange
    */
    public Comparable[] getValues() {

    Comparable[] values = new Comparable[size()];

    values[0] = lowValue;
    values[1] = highValue;

    return values;
    }

    /**
    * Method getLow() returns the lower of the two values in the
    SimpleRange.
    *
    * @return Comparable the lower of the two values in the SimpleRange
    */
    public Comparable getLow() {

    return lowValue;
    }

    /**
    * Method getHigh() returns the higher of the two values in the
    SimpleRange.
    *
    * @return Comparable the higher of the two values in the SimpleRange
    */
    public Comparable getHigh() {

    return highValue;
    }

    /**
    * This version of between() is a convenience method that determines if
    a given int
    * value is within a range of int values.
    *
    * <p>This version of between() will return true if the input value
    matches
    * either end-point of the range or lies anywhere between them.</p>
    *
    * @param Comparable input the value which is being compared to the
    range
    * @return boolean true if the input value is equal to either endpoint
    or lies between them, otherwise false
    */
    public boolean between(Comparable input) {

    return between(true, input, true);
    }


    /**
    * This version of between() determines if a given value is within a
    range of values.
    *
    * <p>Since different users have different expectations about the
    behaviour of a
    * between method, this method provides two booleans that control which
    behaviour
    * will be experienced. For example, if the user desires between to be
    true if the input
    * value is strictly between the two endpoints but not equal to any of
    them, both booleans
    * should be set to false.</p>
    *
    * @param includesLow if true the low end of the range should include
    the low value, otherwise the low end of the range starts just above the low
    value
    * @param input the value that is being compared to the range
    * @param includesHigh if true the high end of the range should include
    the high value, otherwise the high end of the range ends just below the high
    value
    * @return true if the input value is in the range, otherwise false
    */
    public boolean between(boolean includesLow, Comparable input, boolean
    includesHigh) {

    /*
    * If 'includesLow' and 'includesHigh' are both true, return true if
    'input'
    * satisfies this condition: lowValue <= input <= highValue.
    */
    if (includesLow && includesHigh) {
    try {
    if (input.compareTo(lowValue) >= 0 &&
    input.compareTo(highValue) <= 0) {
    return true;
    } else {
    return false;
    }
    } catch (ClassCastException cc_excp) {
    throw new IllegalArgumentException("The input value, " +
    input.toString() + ", belongs to class " + input.getClass().getName() + ".
    Although the end-points of the range can be compared with each other, they
    cannot be compared with the input value.");
    }
    }
    else
    /*
    * If 'includesLow' and 'includesHigh' are both false, return true
    if 'input'
    * satisfies this condition: lowValue < input < highValue.
    */
    if (!includesLow && !includesHigh) {
    try {
    if (input.compareTo(lowValue) > 0 &&
    input.compareTo(highValue) < 0) {
    return true;
    } else {
    return false;
    }
    } catch (ClassCastException cc_excp) {
    throw new IllegalArgumentException("The input value, " +
    input.toString() + ", belongs to class " + input.getClass().getName() + ".
    Although the end-points of the range can be compared with each other, they
    cannot be compared with the input value.");
    }
    }
    else
    /*
    * If 'includesLow' is true and 'includesHigh' is false, return true
    if 'input'
    * satisfies this condition: lowValue <= input < highValue.
    */
    if (includesLow && !includesHigh) {
    try {
    if (input.compareTo(lowValue) >= 0 &&
    input.compareTo(highValue) < 0) {
    return true;
    } else {
    return false;
    }
    } catch (ClassCastException cc_excp) {
    throw new IllegalArgumentException("The input value, " +
    input.toString() + ", belongs to class " + input.getClass().getName() + ".
    Although the end-points of the range can be compared with each other, they
    cannot be compared with the input value.");
    }
    }
    else
    /*
    * If 'includesLow' is false and 'includesHigh' is true, return true
    if 'input'
    * satisfies this condition: lowValue < input <= highValue.
    */
    if (!includesLow && includesHigh) {
    try {
    if (input.compareTo(lowValue) > 0 &&
    input.compareTo(highValue) <= 0) {
    return true;
    } else {
    return false;
    }
    } catch (RuntimeException excp) {
    throw new IllegalArgumentException("The input value, " +
    input.toString() + ", belongs to class " + input.getClass().getName() + ".
    Although the end-points of the range can be compared with each other, they
    cannot be compared with the input value.");
    }
    }
    else {
    return false;
    }
    }
    }
     
    Rhino, Feb 24, 2005
    #18
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. jstorta
    Replies:
    3
    Views:
    475
    jstorta
    Feb 20, 2006
  2. Mr. SweatyFinger
    Replies:
    2
    Views:
    2,215
    Smokey Grindel
    Dec 2, 2006
  3. bill
    Replies:
    5
    Views:
    367
  4. S.Volkov
    Replies:
    2
    Views:
    242
    S.Volkov
    Mar 12, 2006
  5. Trans
    Replies:
    8
    Views:
    351
    Robert Klemme
    Oct 23, 2008
Loading...

Share This Page