1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.logging;
19
20import dalvik.system.VMStack;
21import java.io.IOException;
22import java.io.ObjectInputStream;
23import java.io.Serializable;
24import java.util.ArrayList;
25import java.util.List;
26import java.util.Locale;
27import java.util.MissingResourceException;
28import java.util.ResourceBundle;
29import libcore.util.Objects;
30
31/**
32 * {@code Level} objects are used to indicate the level of logging. There are a
33 * set of predefined logging levels, each associated with an integer value.
34 * Enabling a certain logging level also enables all logging levels with larger
35 * values.
36 * <p>
37 * The predefined levels in ascending order are FINEST, FINER, FINE, CONFIG,
38 * INFO, WARNING, SEVERE. There are two additional predefined levels, which are
39 * ALL and OFF. ALL indicates logging all messages, and OFF indicates logging no
40 * messages.
41 */
42public class Level implements Serializable {
43
44    private static final long serialVersionUID = -8176160795706313070L;
45
46    private static final List<Level> levels = new ArrayList<Level>(9);
47
48    /**
49     * The OFF level provides no logging messages.
50     */
51    public static final Level OFF = new Level("OFF", Integer.MAX_VALUE);
52
53    /**
54     * The SEVERE level provides severe failure messages.
55     */
56    public static final Level SEVERE = new Level("SEVERE", 1000);
57
58    /**
59     * The WARNING level provides warnings.
60     */
61    public static final Level WARNING = new Level("WARNING", 900);
62
63    /**
64     * The INFO level provides informative messages.
65     */
66    public static final Level INFO = new Level("INFO", 800);
67
68    /**
69     * The CONFIG level provides static configuration messages.
70     */
71    public static final Level CONFIG = new Level("CONFIG", 700);
72
73    /**
74     * The FINE level provides tracing messages.
75     */
76    public static final Level FINE = new Level("FINE", 500);
77
78    /**
79     * The FINER level provides more detailed tracing messages.
80     */
81    public static final Level FINER = new Level("FINER", 400);
82
83    /**
84     * The FINEST level provides highly detailed tracing messages.
85     */
86    public static final Level FINEST = new Level("FINEST", 300);
87
88    /**
89     * The ALL level provides all logging messages.
90     */
91    public static final Level ALL = new Level("ALL", Integer.MIN_VALUE);
92
93    /**
94     * Parses a level name into a {@code Level} object.
95     *
96     * @param name
97     *            the name of the desired {@code level}, which cannot be
98     *            {@code null}.
99     * @return the level with the specified name.
100     * @throws NullPointerException
101     *             if {@code name} is {@code null}.
102     * @throws IllegalArgumentException
103     *             if {@code name} is not valid.
104     */
105    public static Level parse(String name) throws IllegalArgumentException {
106        if (name == null) {
107            throw new NullPointerException("name == null");
108        }
109
110        boolean isNameAnInt;
111        int nameAsInt;
112        try {
113            nameAsInt = Integer.parseInt(name);
114            isNameAnInt = true;
115        } catch (NumberFormatException e) {
116            nameAsInt = 0;
117            isNameAnInt = false;
118        }
119
120        synchronized (levels) {
121            for (Level level : levels) {
122                if (name.equals(level.getName())) {
123                    return level;
124                }
125            }
126
127            if (isNameAnInt) {
128                /*
129                 * Loop through levels a second time, so that the returned
130                 * instance will be passed on the order of construction.
131                 */
132                for (Level level : levels) {
133                    if (nameAsInt == level.intValue()) {
134                        return level;
135                    }
136                }
137            }
138        }
139
140        if (!isNameAnInt) {
141            throw new IllegalArgumentException("Cannot parse name '" + name + "'");
142        }
143
144        return new Level(name, nameAsInt);
145    }
146
147    /**
148     * The name of this Level.
149     *
150     * @serial
151     */
152    private final String name;
153
154    /**
155     * The integer value indicating the level.
156     *
157     * @serial
158     */
159    private final int value;
160
161    /**
162     * The name of the resource bundle used to localize the level name.
163     *
164     * @serial
165     */
166    private final String resourceBundleName;
167
168    /**
169     * The resource bundle associated with this level, used to localize the
170     * level name.
171     */
172    private transient ResourceBundle rb;
173
174    /**
175     * Constructs an instance of {@code Level} taking the supplied name and
176     * level value.
177     *
178     * @param name
179     *            the name of the level.
180     * @param level
181     *            an integer value indicating the level.
182     * @throws NullPointerException
183     *             if {@code name} is {@code null}.
184     */
185    protected Level(String name, int level) {
186        this(name, level, null);
187    }
188
189    /**
190     * Constructs an instance of {@code Level} taking the supplied name, level
191     * value and resource bundle name.
192     *
193     * @param name
194     *            the name of the level.
195     * @param level
196     *            an integer value indicating the level.
197     * @param resourceBundleName
198     *            the name of the resource bundle to use.
199     * @throws NullPointerException
200     *             if {@code name} is {@code null}.
201     */
202    protected Level(String name, int level, String resourceBundleName) {
203        if (name == null) {
204            throw new NullPointerException("name == null");
205        }
206        this.name = name;
207        this.value = level;
208        this.resourceBundleName = resourceBundleName;
209        if (resourceBundleName != null) {
210            try {
211                rb = ResourceBundle.getBundle(resourceBundleName,
212                        Locale.getDefault(), VMStack.getCallingClassLoader());
213            } catch (MissingResourceException e) {
214                rb = null;
215            }
216        }
217        synchronized (levels) {
218            levels.add(this);
219        }
220    }
221
222    /**
223     * Gets the name of this level.
224     *
225     * @return this level's name.
226     */
227    public String getName() {
228        return this.name;
229    }
230
231    /**
232     * Gets the name of the resource bundle associated with this level.
233     *
234     * @return the name of this level's resource bundle.
235     */
236    public String getResourceBundleName() {
237        return this.resourceBundleName;
238    }
239
240    /**
241     * Gets the integer value indicating this level.
242     *
243     * @return this level's integer value.
244     */
245    public final int intValue() {
246        return this.value;
247    }
248
249    /**
250     * Serialization helper method to maintain singletons and add any new
251     * levels.
252     *
253     * @return the resolved instance.
254     */
255    private Object readResolve() {
256        synchronized (levels) {
257            for (Level level : levels) {
258                if (value != level.value) {
259                    continue;
260                }
261                if (!name.equals(level.name)) {
262                    continue;
263                }
264                if (Objects.equal(resourceBundleName, level.resourceBundleName)) {
265                    return level;
266                }
267            }
268            // This is a new value, so add it.
269            levels.add(this);
270            return this;
271        }
272    }
273
274    /**
275     * Serialization helper to setup transient resource bundle instance.
276     *
277     * @param in
278     *            the input stream to read the instance data from.
279     * @throws IOException
280     *             if an IO error occurs.
281     * @throws ClassNotFoundException
282     *             if a class is not found.
283     */
284    private void readObject(ObjectInputStream in) throws IOException,
285            ClassNotFoundException {
286        in.defaultReadObject();
287        if (resourceBundleName != null) {
288            try {
289                rb = ResourceBundle.getBundle(resourceBundleName);
290            } catch (MissingResourceException e) {
291                rb = null;
292            }
293        }
294    }
295
296    /**
297     * Gets the localized name of this level. The default locale is used. If no
298     * resource bundle is associated with this level then the original level
299     * name is returned.
300     *
301     * @return the localized name of this level.
302     */
303    public String getLocalizedName() {
304        if (rb == null) {
305            return name;
306        }
307
308        try {
309            return rb.getString(name);
310        } catch (MissingResourceException e) {
311            return name;
312        }
313    }
314
315    /**
316     * Compares two {@code Level} objects for equality. They are considered to
317     * be equal if they have the same level value.
318     *
319     * @param o
320     *            the other object to compare this level to.
321     * @return {@code true} if this object equals to the supplied object,
322     *         {@code false} otherwise.
323     */
324    @Override
325    public boolean equals(Object o) {
326        if (this == o) {
327            return true;
328        }
329
330        if (!(o instanceof Level)) {
331            return false;
332        }
333
334        return ((Level) o).intValue() == this.value;
335    }
336
337    /**
338     * Returns the hash code of this {@code Level} object.
339     *
340     * @return this level's hash code.
341     */
342    @Override
343    public int hashCode() {
344        return this.value;
345    }
346
347    /**
348     * Returns the string representation of this {@code Level} object. In
349     * this case, it is the level's name.
350     *
351     * @return the string representation of this level.
352     */
353    @Override
354    public final String toString() {
355        return this.name;
356    }
357}
358