1/*
2 * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.*;
25import java.lang.ref.Reference;
26import java.lang.ref.ReferenceQueue;
27import java.lang.ref.WeakReference;
28import java.lang.reflect.InvocationTargetException;
29import java.lang.reflect.Method;
30import java.net.MalformedURLException;
31import java.net.URL;
32import java.net.URLClassLoader;
33import java.util.*;
34import java.util.logging.*;
35
36/*
37 * @test
38 * @bug 8026027 6543126
39 * @summary Test Level.parse to look up custom levels by name and its
40 *          localized name
41 *
42 * @run main/othervm CustomLevel
43 */
44
45public class CustomLevel extends Level {
46    public CustomLevel(String name, int value, String resourceBundleName) {
47        super(name, value, resourceBundleName);
48    }
49
50    private static final List<Level> levels = new ArrayList<>();
51    private static final String RB_NAME = "myresource";
52    private static final String OTHERRB_NAME = "myresource2";
53
54    private static class CustomLevelReference extends WeakReference<Level> {
55        final String name;
56        final int value;
57        final String resourceBundleName;
58        public CustomLevelReference(Level level, ReferenceQueue<Level> queue) {
59            super(level, queue);
60            name = level.getName();
61            value = level.intValue();
62            resourceBundleName = level.getResourceBundleName();
63        }
64
65        @Override
66        public String toString() {
67            return "CustomLevelReference(\"" + name + "\", " + value + ", \""
68                    + resourceBundleName + "\")";
69        }
70
71    }
72
73    public static void main(String[] args) throws Exception {
74        setupCustomLevels();
75        setUpCustomLevelsOtherLoader();
76
77        // Level.parse will return the custom Level instance
78        for (Level level : levels) {
79            final ResourceBundle rb = getResourceBundle(level);
80            String name = level.getName();
81            Level l = Level.parse(name);
82            if (!name.equals("WARNING") && !name.equals("INFO")
83                 && !name.equals("SEVERE")) {
84                // custom level whose name doesn't conflict with any standard one
85                checkCustomLevel(l, level);
86            } else if (l != Level.WARNING && l != Level.INFO && l != Level.SEVERE
87                    || !name.equals(l.getName())) {
88                throw new RuntimeException("Unexpected level " + formatLevel(l));
89            }
90            System.out.println("Level.parse found expected level: "
91                            + formatLevel(l));
92            String localizedName = rb.getString(level.getName());
93            l = Level.parse(localizedName);
94            if (l != level) {
95                throw new RuntimeException("Unexpected level " + l + " "
96                    + l.getClass() + " for " + localizedName
97                    + " in " + rb.getBaseBundleName());
98            }
99        }
100
101        final long otherLevelCount = levels.stream()
102            .filter(CustomLevel::isCustomLoader)
103            .count();
104
105        // Now verify that custom level instances are correctly
106        // garbage collected when no longer referenced
107        ReferenceQueue<Level> queue = new ReferenceQueue<>();
108        List<CustomLevelReference> refs = new ArrayList<>();
109        List<CustomLevelReference> customRefs = new ArrayList<>();
110        int otherLevels = 0;
111        while (!levels.isEmpty()) {
112            Level l = levels.stream().findAny().get();
113            boolean isCustomLoader = isCustomLoader(l);
114            if (isCustomLoader) otherLevels++;
115
116            CustomLevelReference ref = new CustomLevelReference(l, queue);
117            if (isCustomLoader) {
118                customRefs.add(ref);
119            } else {
120                refs.add(ref);
121            }
122
123            // remove strong references to l
124            levels.remove(l);
125            l = null;
126
127            // Run gc and wait for garbage collection
128            if (otherLevels == otherLevelCount) {
129                if (customRefs.size() != otherLevelCount) {
130                    throw new RuntimeException("Test bug: customRefs.size() != "
131                             + otherLevelCount);
132                }
133                waitForGC(customRefs, queue);
134            }
135        }
136        if (otherLevelCount != otherLevels || otherLevelCount == 0) {
137            throw new RuntimeException("Test bug: "
138                + "no or wrong count of levels loaded from custom loader");
139        }
140        if (!customRefs.isEmpty()) {
141            throw new RuntimeException(
142                "Test bug: customRefs.size() should be empty!");
143        }
144        while (!refs.isEmpty()) {
145            final Reference<?> ref = refs.remove(0);
146            if (ref.get() == null) {
147                throw new RuntimeException("Unexpected garbage collection for "
148                           + ref);
149            }
150        }
151    }
152
153    private static void waitForGC(List<CustomLevelReference> customRefs,
154                                  ReferenceQueue<Level> queue)
155         throws InterruptedException
156    {
157        while (!customRefs.isEmpty()) {
158            Reference<? extends Level> ref2;
159            do {
160                System.gc();
161                Thread.sleep(100);
162            } while ((ref2 = queue.poll()) == null);
163
164            // Check garbage collected reference
165            if (!customRefs.contains(ref2)) {
166               throw new RuntimeException("Unexpected reference: " + ref2);
167            }
168            CustomLevelReference ref = customRefs.remove(customRefs.indexOf(ref2));
169            System.out.println(ref2 + " garbage collected");
170            final String name = ref.name;
171            Level l;
172            try {
173                l = Level.parse(name);
174                if (!name.equals("SEVERE")
175                    && !name.equals("INFO")
176                    || !name.equals(l.getName())) {
177                    throw new RuntimeException("Unexpected level "
178                            + formatLevel(l));
179                } else {
180                    if (l == Level.WARNING || l == Level.INFO
181                            || l == Level.SEVERE) {
182                        System.out.println("Level.parse found expected level: "
183                                + formatLevel(l));
184                    } else {
185                        throw new RuntimeException("Unexpected level "
186                            + formatLevel(l));
187                    }
188                }
189            } catch (IllegalArgumentException iae) {
190                if (!name.equals("WARNING")
191                    && !name.equals("INFO")
192                    && !name.equals("SEVERE")) {
193                    System.out.println("Level.parse fired expected exception: "
194                        + iae);
195                } else {
196                    throw iae;
197                }
198            }
199        }
200    }
201
202    private static boolean isCustomLoader(Level level) {
203        final ClassLoader cl = level.getClass().getClassLoader();
204        return cl != null
205             && cl != ClassLoader.getPlatformClassLoader()
206             && cl != ClassLoader.getSystemClassLoader();
207    }
208
209    static ResourceBundle getResourceBundle(Level level) {
210        return isCustomLoader(level)
211            ? ResourceBundle.getBundle(OTHERRB_NAME, Locale.getDefault(),
212                                       level.getClass().getClassLoader())
213            : ResourceBundle.getBundle(RB_NAME);
214    }
215
216    private static void setupCustomLevels() throws IOException {
217        levels.add(new CustomLevel("EMERGENCY", 1090, RB_NAME));
218        levels.add(new CustomLevel("ALERT", 1060, RB_NAME));
219        levels.add(new CustomLevel("CRITICAL", 1030, RB_NAME));
220        levels.add(new CustomLevel("WARNING", 1010, RB_NAME));
221        levels.add(new CustomLevel("INFO", 1000, RB_NAME));
222    }
223
224    static void setUpCustomLevelsOtherLoader()
225         throws MalformedURLException,
226               ClassNotFoundException, NoSuchMethodException,
227               IllegalAccessException, InvocationTargetException
228    {
229        final String classes = System.getProperty("test.classes",
230                                                  "build/classes");
231        final String sources = System.getProperty("test.src",
232                                                  "src");
233        final URL curl = new File(classes).toURI().toURL();
234        final URL surl = new File(sources).toURI().toURL();
235        URLClassLoader loader = new URLClassLoader(new URL[] {curl, surl},
236                                     ClassLoader.getPlatformClassLoader());
237        Class<?> customLevelClass = Class.forName("CustomLevel", false, loader);
238        Method m = customLevelClass.getMethod("setUpCustomLevelsOtherLoader",
239                                              List.class);
240        m.invoke(null, levels);
241    }
242
243    public static void setUpCustomLevelsOtherLoader(List<Level> levels) {
244        levels.add(new CustomLevel("OTHEREMERGENCY", 1091, OTHERRB_NAME));
245        levels.add(new CustomLevel("OTHERALERT", 1061, OTHERRB_NAME));
246        levels.add(new CustomLevel("OTHERCRITICAL", 1031, OTHERRB_NAME));
247        levels.add(new CustomLevel("SEVERE", 1011, OTHERRB_NAME));
248        levels.add(new CustomLevel("INFO", 1000, OTHERRB_NAME));
249    }
250
251    static void checkCustomLevel(Level level, Level expected) {
252        // Level value must be the same
253        if (!level.equals(expected)) {
254            throw new RuntimeException(formatLevel(level) + " != "
255                 + formatLevel(expected));
256        }
257
258        if (!level.getName().equals(expected.getName())) {
259            throw new RuntimeException(formatLevel(level) + " != "
260                 + formatLevel(expected));
261        }
262
263        // Level.parse is expected to return the custom Level
264        if (level != expected) {
265            throw new RuntimeException(formatLevel(level) + " != "
266                 + formatLevel(expected));
267        }
268
269        final ResourceBundle rb = getResourceBundle(level);
270        String name = rb.getString(level.getName());
271        if (!level.getLocalizedName().equals(name)) {
272            // must have the same localized name
273            throw new RuntimeException(level.getLocalizedName() + " != " + name);
274        }
275    }
276
277    static String formatLevel(Level l) {
278        return l + ":" + l.intValue() + ":" + l.getClass().getName();
279    }
280}
281