*static Map in the extensible enum class

On Sun, Jan 26, 2014 at 2:56 PM, Remko Popma <remko.po...@gmail.com> wrote:

> After thinking about it some more, the code generation could just access
> the static Map in the extensible enums class and find out what instances
> are registered. That would work but assumes that the configuration (or at
> least the initialization of the static Map in Levels) is complete before
> code generation.
>
>
> On Sun, Jan 26, 2014 at 2:49 PM, Remko Popma <remko.po...@gmail.com>wrote:
>
>> Did you mean that a custom Level implementation cannot extend StdLevel?
>> So a custom Level implementation would need to include the levels defined
>> in StdLevel (DEBUG, INFO, ...) in addition to the custom levels.
>> I think I see now.
>>
>> How much of a problem is this though? Or, do we need to start thinking
>> about the code generation bit in order to be able to decide if the
>> trade-off is worth it?
>>
>>
>>
>> On Sun, Jan 26, 2014 at 2:39 PM, Remko Popma <remko.po...@gmail.com>wrote:
>>
>>> I don't think client code can do new Level(){} as the constructor
>>> requires String and int arguments.
>>>
>>> By the way, I am unclear on what went wrong with the enum approach you
>>> originally took.
>>> You said:
>>>     StdLevel isn’t a Level because it can’t extend it if it is an enum,
>>> so I can’t initialize the levels using that.
>>>
>>> I don't understand. StdLevel implements the Level interface, right? So
>>> what do you mean by "it can't extend it"?
>>>
>>> The reason I ask is that for the code generation, "real" java enums may
>>> be easier to deal with than the extensible enum approach.
>>>
>>>
>>> On Sun, Jan 26, 2014 at 2:30 PM, Ralph Goers <ralph.go...@dslextreme.com
>>> > wrote:
>>>
>>>> Out of curiosity, what exactly is the benefit of declaring the class
>>>> abstract when it has a protected constructor?  It seems like all you are
>>>> accomplishing is making the instantiation syntax uglier. It also bothers me
>>>> that open code can just do a new Level(){} - which will do nothing but
>>>> cause problems.  I’m beginning to think that we should require an
>>>> annotation on the Level declaration of the extension class to avoid that.
>>>>
>>>> Ralph
>>>>
>>>> On Jan 25, 2014, at 9:13 PM, Remko Popma <remko.po...@gmail.com> wrote:
>>>>
>>>> Ralph,
>>>> I copied Nick's code _as is_ and had no compile errors.
>>>> The class is abstract, but instances are defined in the static block as:
>>>> OFF = new Level("OFF", 0) {}; // note the {}: this creates an anonymous
>>>> concrete subclass
>>>>
>>>> I agree that read access needs to be synchronized as well, not just
>>>> write access (the constructor).
>>>> I experimented with several options:
>>>> * synchronizing on plain Object in both constructor and when accessing
>>>> the static Map(s)
>>>> * a ReentrantReadWriteLock
>>>> * a lock-free implementation
>>>>
>>>> I decided against ReentrantReadWriteLock as it has more overhead than
>>>> plain synchronized access and the write access (in the constructors) is
>>>> going to be extremely rare: not worth paying the overhead in the more
>>>> common reads. It is also cumbersome to code.
>>>>
>>>> The lock-free implementation uses an AtomicInteger for the ordinals,
>>>> and an AtomicReference for the Map<String, Level>.
>>>> In the constructor, create a new Map<String, Level> instance based on
>>>> the old copy, add the new instance, and try to call compareAndSet to
>>>> replace the old instance with the new instance. Retry on failure.
>>>>
>>>> Finally, simply synchronizing on the constructorLock object in the
>>>> Level.toLevel() and Level.values() methods may be simplest.
>>>>
>>>> Which of the last two is best depends on how often the toLevel() and
>>>> values() levels are called.
>>>> It turns out they are only called during reconfiguration, so no real
>>>> need to optimize these methods.
>>>> I would argue that simple synchronization may be best in this case.
>>>>
>>>> Remko
>>>>
>>>>
>>>>
>>>> On Sun, Jan 26, 2014 at 1:49 PM, Ralph Goers <
>>>> ralph.go...@dslextreme.com> wrote:
>>>>
>>>>> As I am working on this I just want to point out a number of issues
>>>>> with the code below:
>>>>>
>>>>> 1. The class is abstract. The static block is doing a bunch of new
>>>>> Level() invocations which obviously generate compile errors on an abstract
>>>>> class.  I had to make it be a non-abstract class.
>>>>> 2. As I pointed out before there is no way to access the “standard”
>>>>> levels as an enum. I have addressed that.
>>>>> 3. Although the constructor is synchronized access to the Map is not.
>>>>> Trying to get from the map while a Level is being added will result in a
>>>>> ConcurrentModificationException. I am using a ConcurrentMap instead.
>>>>> 3. The constructor requires synchronization because it is modifying
>>>>> both the map and the ordinal. However, since this isn’t an enum the 
>>>>> ordinal
>>>>> value is of dubious value. Removing that would allow the removal of the
>>>>> synchronization in the constructor. I am considering that but I haven’t
>>>>> done it yet.
>>>>> 4. Your example of creating the extension shows doing a new Level().
>>>>> This doesn’t work because a) the class is abstract and b) the constructor
>>>>> is protected. I am leaving the constructor protected so extension will
>>>>> require doing new ExtendedLevel(name, value) and creating a constructor.
>>>>> Not requiring that means applications can do a new Level() anywhere and I
>>>>> am opposed to allowing that.
>>>>>
>>>>> Ralph
>>>>>
>>>>> On Jan 23, 2014, at 12:42 AM, Nick Williams <
>>>>> nicho...@nicholaswilliams.net> wrote:
>>>>>
>>>>> > Okay, I finally got a minute to read all of these emails, and...
>>>>> >
>>>>> > EVERYBODY FREEZE!
>>>>> >
>>>>> > What if I could get you an extensible enum that required no
>>>>> interface changes and no binary-incompatible changes at all? Sound too 
>>>>> good
>>>>> to be true? I proposed this months ago (LOG4J2-41) and it got shot down
>>>>> multiple times, but as of now I've heard THREE people say "extensible 
>>>>> enum"
>>>>> in this thread, so here it is, an extensible enum:
>>>>> >
>>>>> > public abstract class Level implements Comparable<Level>,
>>>>> Serializable {
>>>>> >    public static final Level OFF;
>>>>> >    public static final Level FATAL;
>>>>> >    public static final Level ERROR;
>>>>> >    public static final Level WARN;
>>>>> >    public static final Level INFO;
>>>>> >    public static final Level DEBUG;
>>>>> >    public static final Level TRACE;
>>>>> >    public static final Level ALL;
>>>>> >
>>>>> >
>>>>> >    private static final long serialVersionUID = 0L;
>>>>> >    private static final Hashtable<String, Level> map;
>>>>> >    private static final TreeMap<Integer, Level> values;
>>>>> >    private static final Object constructorLock;
>>>>> >
>>>>> >
>>>>> >    static {
>>>>> >        // static variables must be constructed in certain order
>>>>> >        constructorLock = new Object();
>>>>> >        map = new Hashtable<String, Level>();
>>>>> >        values = new TreeMap<Integer, Level>();
>>>>> >        OFF = new Level("OFF", 0) {};
>>>>> >        FATAL = new Level("FATAL", 100) {};
>>>>> >        ERROR = new Level("ERROR", 200) {};
>>>>> >        WARN = new Level("WARN", 300) {};
>>>>> >        INFO = new Level("INFO", 400) {};
>>>>> >        DEBUG = new Level("DEBUG", 500) {};
>>>>> >        TRACE = new Level("TRACE", 600) {};
>>>>> >        ALL = new Level("ALL", Integer.MAX_VALUE) {};
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    private static int ordinals;
>>>>> >
>>>>> >
>>>>> >    private final String name;
>>>>> >    private final int intLevel;
>>>>> >    private final int ordinal;
>>>>> >
>>>>> >
>>>>> >    protected Level(String name, int intLevel) {
>>>>> >        if(name == null || name.length() == 0)
>>>>> >            throw new IllegalArgumentException("Illegal null Level
>>>>> constant");
>>>>> >        if(intLevel < 0)
>>>>> >            throw new IllegalArgumentException("Illegal Level int
>>>>> less than zero.");
>>>>> >        synchronized (Level.constructorLock) {
>>>>> >            if(Level.map.containsKey(name.toUpperCase()))
>>>>> >                throw new IllegalArgumentException("Duplicate Level
>>>>> constant [" + name + "].");
>>>>> >            if(Level.values.containsKey(intLevel))
>>>>> >                throw new IllegalArgumentException("Duplicate Level
>>>>> int [" + intLevel + "].");
>>>>> >            this.name = name;
>>>>> >            this.intLevel = intLevel;
>>>>> >            this.ordinal = Level.ordinals++;
>>>>> >            Level.map.put(name.toUpperCase(), this);
>>>>> >            Level.values.put(intLevel, this);
>>>>> >        }
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public int intLevel() {
>>>>> >        return this.intLevel;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public boolean isAtLeastAsSpecificAs(final Level level) {
>>>>> >        return this.intLevel <= level.intLevel;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public boolean isAtLeastAsSpecificAs(final int level) {
>>>>> >        return this.intLevel <= level;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public boolean lessOrEqual(final Level level) {
>>>>> >        return this.intLevel <= level.intLevel;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public boolean lessOrEqual(final int level) {
>>>>> >        return this.intLevel <= level;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    @Override
>>>>> >    @SuppressWarnings("CloneDoesntCallSuperClone")
>>>>> >    public Level clone() throws CloneNotSupportedException {
>>>>> >        throw new CloneNotSupportedException();
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    @Override
>>>>> >    public int compareTo(Level other) {
>>>>> >        return intLevel < other.intLevel ? -1 : (intLevel >
>>>>> other.intLevel ? 1 : 0);
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    @Override
>>>>> >    public boolean equals(Object other) {
>>>>> >        return other instanceof Level && other == this;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public Class<Level> getDeclaringClass() {
>>>>> >        return Level.class;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    @Override
>>>>> >    public int hashCode() {
>>>>> >        return this.name.hashCode();
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public String name() {
>>>>> >        return this.name;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public int ordinal() {
>>>>> >        return this.ordinal;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    @Override
>>>>> >    public String toString() {
>>>>> >        return this.name;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public static Level toLevel(String name) {
>>>>> >        return Level.toLevel(name, Level.DEBUG);
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public static Level toLevel(String name, Level defaultLevel) {
>>>>> >        if(name == null)
>>>>> >            return defaultLevel;
>>>>> >        name = name.toUpperCase();
>>>>> >        if(Level.map.containsKey(name))
>>>>> >            return Level.map.get(name);
>>>>> >        return defaultLevel;
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public static Level[] values() {
>>>>> >        return Level.values.values().toArray(new
>>>>> Level[Level.values.size()]);
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public static Level valueOf(String name) {
>>>>> >        if(name == null)
>>>>> >            throw new IllegalArgumentException("Unknown level
>>>>> constant [" + name + "].");
>>>>> >        name = name.toUpperCase();
>>>>> >        if(Level.map.containsKey(name))
>>>>> >            return Level.map.get(name);
>>>>> >        throw new IllegalArgumentException("Unknown level constant ["
>>>>> + name + "].");
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
>>>>> String name) {
>>>>> >        return Enum.valueOf(enumType, name);
>>>>> >    }
>>>>> >
>>>>> >
>>>>> >    // for deserialization
>>>>> >    protected final Object readResolve() throws ObjectStreamException
>>>>> {
>>>>> >        return Level.valueOf(this.name);
>>>>> >    }
>>>>> > }
>>>>> >
>>>>> > Extending it is easy:
>>>>> >
>>>>> > public final class ExtendedLevels {
>>>>> >    public static final Level MY_LEVEL = new Level("MY_LEVEL", 250)
>>>>> {};
>>>>> > }
>>>>> >
>>>>> > I still and have ALWAYS believed this was the best option. If we
>>>>> used this option, I would be fine with not adding any new Levels because I
>>>>> could add them myself.
>>>>> >
>>>>> > Nick
>>>>> >
>>>>> > On Jan 22, 2014, at 7:04 PM, Remko Popma wrote:
>>>>> >
>>>>> >> This is only a problem for webapps, right?
>>>>> >> Putting log4j jars in WEB-INF/lib avoids that problem (different
>>>>> class loader).
>>>>> >> Apps that really want to share log4j jars with other apps would
>>>>> need to play nice. Such apps would do well to use a naming convention like
>>>>> Gary suggests.
>>>>> >> Otherwise, the last to register would overwrite any previous level
>>>>> with the same name. (Should probably emit a StatusLogger warning.)
>>>>> >>
>>>>> >> Same intLevel for different names should not be a problem.
>>>>> >>
>>>>> >>
>>>>> >> On Thursday, January 23, 2014, Gary Gregory <garydgreg...@gmail.com>
>>>>> wrote:
>>>>> >> Playing devils advocate:
>>>>> >>
>>>>> >> What happens when different apps register levels with the same name
>>>>> and different intLevels?
>>>>> >> What happens when different apps register levels with the same
>>>>> intLevel and different names?
>>>>> >> Should there be a convention that custom level names be FQNs?
>>>>> >>
>>>>> >> Gary
>>>>> >>
>>>>> >>
>>>>> >> On Wed, Jan 22, 2014 at 10:05 PM, Paul Benedict <
>>>>> pbened...@apache.org> wrote:
>>>>> >> As Gary wanted, a new thread....
>>>>> >>
>>>>> >> First, each enum needs an inherit strength. This would be part of
>>>>> the interface. Forgive me if the word "strength" is wrong; but it's the
>>>>> 100, 200, 300, etc. number that triggers the log level. So make sure the
>>>>> interface contains the intLevel() method.
>>>>> >>
>>>>> >> Second, we need to know the name, right? The name probably requires
>>>>> a new method since it can't be extracted from the enum anymore.
>>>>> >>
>>>>> >> public interface Level {
>>>>> >> int intLevel();
>>>>> >> String name();
>>>>> >> }
>>>>> >>
>>>>> >> PS: The intStrength() name seems hackish. What about strength() or
>>>>> treshold()?
>>>>> >>
>>>>> >> Third, the registration can be done manually by providing a static
>>>>> method (as your did Remko) that the client needs to invoke, or you could
>>>>> have a class-path scanning mechanism. For the latter, you could introduce 
>>>>> a
>>>>> new annotation to be placed on the enum class.
>>>>> >>
>>>>> >> @CustomLevels
>>>>> >> public enum MyCustomEnums {
>>>>> >> }
>>>>> >>
>>>>> >> Paul
>>>>> >>
>>>>> >> On Wed, Jan 22, 2014 at 8:52 PM, Remko Popma <remko.po...@gmail.com>
>>>>> wrote:
>>>>> >> Paul, can you give a bit more detail?
>>>>> >>
>>>>> >> I tried this: copy the current Level enum to a new enum called
>>>>> "Levels" in the same package (other name would be fine too). Then change
>>>>> Level to an interface (removing the constants and static methods, keeping
>>>>> only the non-static methods). Finally make the Levels enum implement the
>>>>> Level interface.
>>>>> >>
>>>>> >> After this, we need to do a find+replace for the references to
>>>>> Level.CONSTANT to Levels.CONSTANT and Level.staticMethod() to
>>>>> Levels.staticMethod().
>>>>> >>
>>>>> >> Finally, the interesting part: how do users add or register their
>>>>> custom levels and how do we enable the Levels.staticLookupMethod(String,
>>>>> Level) to recognize these custom levels?
>>>>> >>
>>>>> >>
>>>>> >>
>>>>> >> On Thursday, January 23, 2014, Paul Benedict <pbened...@apache.org>
>>>>> wrote:
>>>>> >> Agreed. This is not an engineering per se, but really more about if
>>>>> the feature set makes sense.
>>>>> >>
>>>>> >> Well if you guys ever look into the interface idea, you'll give
>>>>> log4j the feature of getting enums to represent custom levels. That's
>>>>> pretty cool, IMO. I don't know if any other logging framework has that and
>>>>> that would probably get some positive attention. It shouldn't be so hard 
>>>>> to
>>>>> do a find+replace on the code that accepts Level and replace it with
>>>>> another name. Yes, there will be some minor refactoring that goes with it,
>>>>> but hard? It shouldn't be.
>>>>> >>
>>>>> >> A name I propose for the interface is LevelDefinition.
>>>>> >>
>>>>> >> Paul
>>>>> >>
>>>>> >>
>>>>> >> On Wed, Jan 22, 2014 at 6:48 PM, Gary Gregory <
>>>>> garydgreg...@gmail.com> wrote:
>>>>> >> Hi, I do not see this as an engineering problem but more a feature
>>>>> set definition issue. So while there may be lots of more or less 
>>>>> internally
>>>>> complicated ways of solving this with interfaces, makers and whatnots, the
>>>>> built in levels are the most user friendly.
>>>>> >>
>>>>> >> I have have lots of buttons, knobs and settings on my sound system
>>>>> that I do not use, just like I do not use all the methods in all the
>>>>> classes in the JRE...
>>>>> >>
>>>>> >> Gary
>>>>> >>
>>>>> >>
>>>>> >>
>>>>> >>
>>>>> >> --
>>>>> >> E-Mail: garydgreg...@gmail.com | ggreg...@apache.org
>>>>> >> Java Persistence with Hibernate, Second Edition
>>>>> >> JUnit in Action, Second Edition
>>>>> >> Spring Batch in Action
>>>>> >> Blog: http://garygregory.wordpress.com
>>>>> >> Home: http://garygregory.com/
>>>>> >> Tweet! http://twitter.com/GaryGregory
>>>>> >
>>>>> >
>>>>> > ---------------------------------------------------------------------
>>>>> > To unsubscribe, e-mail: log4j-dev-unsubscr...@logging.apache.org
>>>>> > For additional commands, e-mail: log4j-dev-h...@logging.apache.org
>>>>> >
>>>>>
>>>>>
>>>>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: log4j-dev-unsubscr...@logging.apache.org
>>>>> For additional commands, e-mail: log4j-dev-h...@logging.apache.org
>>>>>
>>>>>
>>>>
>>>>
>>>
>>
>

Reply via email to