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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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: [email protected] | [email protected]
> 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: [email protected]
For additional commands, e-mail: [email protected]