Thread: Typed AttributeMap

Results 1 to 6 of 6
  1. #1 Typed AttributeMap 
    JavaScript Heathen 👹
    frostbit3's Avatar
    Join Date
    Mar 2012
    Age
    27
    Posts
    399
    Thanks given
    366
    Thanks received
    86
    Discord
    View profile
    Rep Power
    70
    I used this in my Widget Framework, Runik.

    It provides a fully-typed attribute map for objects. Based off previous work by CrazyPanda, the difference being you can pass an entire enumeration as the generic type. To learn more about AttributeMaps, CrazyPanda provided an in-depth explanation here: Simple Type Safe Player Attribute System

    AttributeMap
    Code:
    
    import java.util.EnumMap;
    
    /**
     * A wrapper class for an {@link EnumMap} that provide type safety for its values.
     *
     * @author Seven
     * @author frostbit3
     */
    public class AttributeMap<Props extends Enum<Props> & Attribute> {
    
        /**
         * The underlying map of attributes.
         *
         * EnumMap disallows null keys, but allows null values.
         */
        private final EnumMap<Props, AttributeValue<?>> attributes;
    
        public AttributeMap(Class<Props> enumType) {
            this.attributes = new EnumMap<>(enumType);
        }
    
        /**
         * Serves as a helper-function that places an {@link Props} into the map with a specified value type.
         *
         * @Param key
         *    The attribute to place.
         *
         * @Param value
         *    The type safe value.
         *
         * @ThroWs AttributeException
         *    The exception thrown the {@code key}s initial type is different than the specified {@code value}s initial type.
         */
        private <T extends Object> void put(Props key, AttributeValue<T> value) throws AttributeException {
            if (key.defaultValue().getClass() != value.getType()) {
                throw new AttributeException(key, value);
            }
    
            attributes.put(key, value);
        }
    
        /**
         * Places an {@link Props} into the map with a type-safe value.
         *
         * @Param key
         *    The attribute to place.
         *
         * @Param value
         *    The type safe value.
         */
        public <T extends Object> void put(Props key, T value) {
            try {
                put(key, new AttributeValue<T>(value));
            } catch (AttributeException ex) {
                ex.printStackTrace();
            }
        }
    
        /**
         * Places an {@link Props} into the map with a default value.
         *
         * @Param key
         *    The attribute to place.
         *
         */
        public void put(Props key) {
            try {
                put(key, new AttributeValue<>(key.defaultValue()));
            } catch (AttributeException ex) {
                ex.printStackTrace();
            }
        }
    
        /**
         * Places an {@link Props} into the map with a default value.
         *
         * @Param key
         *    The attribute to place.
         *
         */
        public void increment(Props key) {
            try {
                Integer value = value(key);
                put(key, value + 1);
            } catch (AttributeException ex) {
                ex.printStackTrace();
            }
        }
    
        /**
         * Toggles a {@link Boolean} type by switching false to true and true to false.
         *
         * @Param key
         *    The key who's value to toggle.
         */
        public void toggle(Props key) {
            Boolean value = (Boolean) attributes.get(key).getValue();
            try {
                toggle(key, new AttributeValue<Boolean>(value));
            } catch (AttributeException ex) {
                ex.printStackTrace();
            }
        }
    
        /**
         * The helper function which validated the type and toggles the {@link Boolean} value if it is possible to do so.
         *
         * @Param key
         *    The key who's value to toggle.
         *
         * @Param key
         *    The key to toggle.
         */
        private void toggle(Props key, AttributeValue<Boolean> value) throws AttributeException {
            if (key.defaultValue().getClass() != value.getType()) {
                throw new AttributeException(key, value);
            }
            put(key, !value.getValue());
        }
    
        /**
         * Servers as a helper-function which determines if a {@code key} contains a specified {@code value}.
         *
         * @Param key
         *    The attribute to place.
         *
         * @Param value
         *    The type safe value.
         *
         * @Return {@code true} If this map contains the specified value. {@code false} otherwise.
         * @ThroWs AttributeException
         *    The exception thrown the {@code key}s initial type is different than the specified {@code value}s initial type.
         */
        private <T extends Object> boolean contains(Props key, AttributeValue<T> value) throws AttributeException {
            if (key.defaultValue().getClass() != value.getType()) {
                throw new AttributeException(key, value);
            }
            return attributes.get(key).getValue() == value.getValue();
        }
    
        /**
         * Determines if a {@code key} contains a specified {@code value}.
         *
         * @Param key
         *    The attribute to place.
         *
         * @Param value
         *    The type safe value.
         *
         * @Return {@code true} If this map contains the specified value. {@code false} otherwise.
         */
        public <T extends Object> boolean contains(Props key, T value) {
            try {
                return contains(key, new AttributeValue<T>(value));
            } catch (AttributeException ex) {
                ex.printStackTrace();
            }
            return attributes.get(key).getValue() == value;
        }
    
        /**
         * Gets the {@link AttributeValue} for a specified {@code key}.
         *
         * @Param key
         *    The key to get the attribute wrapper value for.
         *
         * @Return The wrapper for the value.
         */
        public AttributeValue<?> attribute(Props key) {
            return attributes.get(key);
        }
    
        /**
         * Gets the value from a specified {@code key}.
         *
         * This will grab the actual value, so you don't have to cast every time to attemp to get
         * the value from a key.
         *
         *  @Return The actual value.
         */
        @suppressWarnings("unchecked")
        public <T extends Object> T value(Props key) {
            return (T) attributes.get(key).getValue();
        }
    
        /**
         * Gets the underlying map of attributes.
         *
         * @Return The underlying map {@link EnumMap}.
         */
        public EnumMap<Props, AttributeValue<?>> map() {
            return this.attributes;
        }
    
        /**
         * Gets a specified value as a {@link String} type.
         *
         * @Param key
         *    The key who's value to get as a string.
         *
         * @Return The value as a string.
         */
        public String valueAsString(Props key) {
            return attributes.get(key).toString();
        }
    
        /**
         * Determines if this map is empty.
         *
         * @Return {@code true} If this map is empty. {@code false} otherwise.
         */
        public boolean isEmpty() {
            return attributes.isEmpty();
        }
    
    }
    AttributeException
    Code:
    
    /**
     * The {@link RuntimeException} implementation specifically for {@link Attribute}s.
     * 
     * @author Seven
     */
    public final class AttributeException extends RuntimeException {
    
          private static final long serialVersionUID = 1L;
    
          /**
           * Creates a new {@link AttributeException}.
           * 
           * @Param key
           *    The key or this attribute.
           *    
           * @Param value
           *    The value for this attribute.
           */
          public AttributeException(Attribute key, AttributeValue<?> value) {
                super(String.format("Invalid value type: %s for [key=%s], only accepts type of %s", value.getType().getSimpleName(), key.name().toLowerCase(), key.defaultValue().getClass().getSimpleName()));
          }
          
    }
    Attribute
    Code:
    
    /**
     * Represents an attribute
     * @author frostbit3
     */
    public interface Attribute {
        Object defaultValue();
        String name();  }
    AttributeValue
    Code:
    
    import java.util.Map;
    
    /**
     * A wrapper class for a {@link Map}s value.
     * 
     * This class can provide additional functions for a maps key values and to help force type safety.
     *
     * @author Seven
     */
    public final class AttributeValue<T extends Object> {
    
          /**
           * The actual value of this {@link AttributeValue}.
           */
          private final T value;
    
          /**
           * Creates a new {@link AttributeValue}.
           * 
           * @Param value
           *    The value to add.
           */
          public AttributeValue(T value) {
                this.value = value;
          }
    
          /**
           * Gets the actual value.
           * 
           * @Return The actual value.
           */
          public T getValue() {
                return value;
          }
          
          /**
           * Gets the type of this value.
           * 
           * @Return The type.
           */
          public Class<?> getType() {
                return value.getClass();
          }
          
          @Override
          public String toString() {
               return value.toString(); 
          }
    
    }

    Example usage:
    Code:
    
    public enum PlayerAttribute implements Attribute {
        BUSY(false),
        IN_COMBAT(false),
        AUTO_RETALIATE(true),
        RUN_ENERGY(100),
        PLAYERS_KILLED(0);
    
        private final Object defaultValue;
    
        PlayerAttribute(Object defaultValue) { this.defaultValue = defaultValue; }
    
        @Override
        public Object defaultValue() { return defaultValue; }
    }
    
    // ...
    
    public class Player {
      
    private AttributeMap<PlayerAttribute> attributes = new AttributeMap<>(PlayerAttribute.class) {{
        put(PlayerAttribute.BUSY);
        put(PlayerAttribute.IN_COMBAT);
        put(PlayerAttribute.RUN_ENERGY);
        put(PlayerAttribute.AUTO_RETALIATE);
    }};
    
    }
    theres probably a better way to assign the defaults, so if known please feel free to comment
    >> real life is pretty much the same as code. off-by-one, naming, and unterminated recursion
    Reply With Quote  
     

  2. #2  
    Chemist

    Advocatus's Avatar
    Join Date
    Dec 2009
    Age
    29
    Posts
    2,555
    Thanks given
    192
    Thanks received
    765
    Discord
    View profile
    Rep Power
    1332
    Imho. The whole idea of any "attributemap" to map values to string... I think I first saw this in rs2server or dementhium like a decade ago and never understood why.

    Code:
       public void increment(Props key) {
            try {
                Integer value = value(key);
    Not saying that people should, but you have a public method there. Pretty sure you would need to add something like you have in ur other methods as a safeguard
    Code:
           if (key.defaultValue().getClass() != value.getType()) {
                throw new AttributeException(key, value);
            }
    to that method since some people are stupid and might call increment on something that they shouldn't and that Integer cast could make it go boom.


    Edit: Just noticed the try cache...lol.
    Quote Originally Posted by blakeman8192 View Post
    Quitting is the only true failure.
    Reply With Quote  
     

  3. Thankful user:


  4. #3  
    Banned

    Join Date
    Jun 2010
    Age
    24
    Posts
    4,887
    Thanks given
    1,698
    Thanks received
    1,597
    Discord
    View profile
    Rep Power
    0
    You're on the right track - Scu11 released something similar to this in the informative threads section, but it was more applicable to varps/varbits. That would be a better application of what direction you're going to imo.
    Reply With Quote  
     

  5. Thankful user:


  6. #4  
    JavaScript Heathen 👹
    frostbit3's Avatar
    Join Date
    Mar 2012
    Age
    27
    Posts
    399
    Thanks given
    366
    Thanks received
    86
    Discord
    View profile
    Rep Power
    70
    Quote Originally Posted by Advocatus View Post
    Imho. The whole idea of any "attributemap" to map values to string... I think I first saw this in rs2server or dementhium like a decade ago and never understood why.
    I suppose it could make sense to use constructor overloading and simple instance variables, however it looks ugly and accounting for every variation makes my head spin.

    Quote Originally Posted by Tyluur View Post
    You're on the right track - Scu11 released something similar to this in the informative threads section, but it was more applicable to varps/varbits. That would be a better application of what direction you're going to imo.
    Oh I didn't know about this, I'll have to take a peek
    >> real life is pretty much the same as code. off-by-one, naming, and unterminated recursion
    Reply With Quote  
     

  7. #5  
    Registered Member
    JayArrowz's Avatar
    Join Date
    Sep 2008
    Posts
    90
    Thanks given
    91
    Thanks received
    94
    Discord
    View profile
    Rep Power
    595
    Looks cool, but not too sure about using the enum type as a key in this map.

    Netty has a cool impl of a attribute map which is thread safe:
    https://github.com/netty/netty/blob/...ributeMap.java

    WARNING As a rule of thumb, enums are code smells and should be refactored to polymorphic classes.
    [8] Seemann, Mark, Dependency Injection in .Net, 2011, p. 342
    [8] Martin Fowler et al., Refactoring: Improving the Design of Existing Code (New York: Addison-Wesley, 1999), 82.

    Java Practice: When NOT to use enum's, ENUM types should not be used if you cannot limit a set of possible values to a few elements. When your application does not know the list of The lesson here is that enums are not classes/objects. Enums are good to represent static/singleton objects but should never be used as value objects or have attributes that get set during usage. You can use an enum to 'type/label' a two week duration, but the actual start/end dates should be attributes of a class DateRange.

    When you should and should NOT use ENUM data type, Between, overuse of enums might mean that your methods do too much (it's often better to have several separate methods, rather than one method that takes several flags which modify what it does), but if you have to use flags or type codes, enums are the way to go. Prefer Enums and Avoid Booleans An enumerator is a data type consisting of a set of named values that can be used in a type-safe way. While it may not look as simple as a boolean, using an enum or

    Beginner's Guide to Java enum, Enums are intended for use cases when you have literally enumerated every possible value a variable could take. Ever. Think use cases like days of the week or Behavior related to the enumeration gets scattered around the application; New enumeration values require shotgun surgery; Enumerations don’t follow the Open-Closed Principle; With enumeration behavior scattered around, we can never bring it back to the source type, because enumeration types can’t have any behavior (or state for that matter).
    Reply With Quote  
     

  8. Thankful user:


  9. #6  
    JavaScript Heathen 👹
    frostbit3's Avatar
    Join Date
    Mar 2012
    Age
    27
    Posts
    399
    Thanks given
    366
    Thanks received
    86
    Discord
    View profile
    Rep Power
    70
    Quote Originally Posted by JayArrowz View Post
    Looks cool, but not too sure about using the enum type as a key in this map.

    Netty has a cool impl of a attribute map which is thread safe:
    https://github.com/netty/netty/blob/...ributeMap.java


    [8] Seemann, Mark, Dependency Injection in .Net, 2011, p. 342
    [8] Martin Fowler et al., Refactoring: Improving the Design of Existing Code (New York: Addison-Wesley, 1999), 82.
    Martin Fowler is great, however I disagree with him on a few ideological things. In this particular case I'm not convinced that ditching enums in favor of polymorphic classes would be the best way to go about things, the DX would be even more verbose than it already is. I suppose one approach that could be taken is using mixin's of some sort, but it would be pretty hacky (i would like to see it! ala Sponge).

    I've been doing more FRP (Functional Reactive Programming) over the past 3 years and this seems more in-line with the languages I've been using, so understandably it looks and feels like I'm putting a "square peg in a round hole". But it works for me, I'm not worried about thread-safety because servers I usually end up using are single-threaded (not a great excuse, an excuse nonetheless ).

    Thanks for the comment Jay!
    >> real life is pretty much the same as code. off-by-one, naming, and unterminated recursion
    Reply With Quote  
     

  10. Thankful user:



Thread Information
Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)


User Tag List

Similar Threads

  1. [GIMP] Ink Blot/Abstract Type Sigs
    By Runite in forum Tutorials
    Replies: 9
    Last Post: 09-06-2007, 06:09 PM
  2. New scimmi /pirate type swordhjj
    By Bobster in forum RS2 Client
    Replies: 23
    Last Post: 06-23-2007, 02:05 AM
  3. The New Type Of Sprite
    By Looted in forum Showcase
    Replies: 1
    Last Post: 06-17-2007, 05:17 PM
  4. Dragon hat wizard type helm thingy?
    By haz425 in forum Showcase
    Replies: 3
    Last Post: 06-08-2007, 09:23 PM
Tags for this Thread

View Tag Cloud

Posting Permissions
  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •