| /* |
| * Copyright (c) 2007, 2008, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.Serializable; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.management.ManagementFactory; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.AccessibleObject; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import javax.management.Attribute; |
| import javax.management.AttributeList; |
| import javax.management.AttributeNotFoundException; |
| import javax.management.DynamicMBean; |
| import javax.management.InstanceAlreadyExistsException; |
| import javax.management.InstanceNotFoundException; |
| import javax.management.IntrospectionException; |
| import javax.management.InvalidAttributeValueException; |
| import javax.management.ListenerNotFoundException; |
| import javax.management.MBeanAttributeInfo; |
| import javax.management.MBeanConstructorInfo; |
| import javax.management.MBeanException; |
| import javax.management.MBeanInfo; |
| import javax.management.MBeanNotificationInfo; |
| import javax.management.MBeanOperationInfo; |
| import javax.management.MBeanRegistration; |
| import javax.management.MBeanRegistrationException; |
| import javax.management.MBeanServer; |
| import javax.management.MBeanServerBuilder; |
| import javax.management.MBeanServerConnection; |
| import javax.management.MBeanServerDelegate; |
| import javax.management.MBeanServerFactory; |
| import javax.management.MBeanServerNotification; |
| import javax.management.MalformedObjectNameException; |
| import javax.management.NotCompliantMBeanException; |
| import javax.management.Notification; |
| import javax.management.NotificationBroadcaster; |
| import javax.management.NotificationBroadcasterSupport; |
| import javax.management.NotificationEmitter; |
| import javax.management.NotificationFilter; |
| import javax.management.NotificationListener; |
| import javax.management.ObjectInstance; |
| import javax.management.ObjectName; |
| import javax.management.OperationsException; |
| import javax.management.QueryEval; |
| import javax.management.QueryExp; |
| import javax.management.ReflectionException; |
| import javax.management.RuntimeErrorException; |
| import javax.management.RuntimeMBeanException; |
| import javax.management.StandardMBean; |
| import javax.management.loading.ClassLoaderRepository; |
| import javax.management.remote.JMXConnector; |
| import javax.management.remote.JMXConnectorFactory; |
| import javax.management.remote.JMXConnectorServer; |
| import javax.management.remote.JMXConnectorServerFactory; |
| import javax.management.remote.JMXServiceURL; |
| |
| /* |
| * @test OldMBeanServerTest.java |
| * @bug 5072268 |
| * @summary Test that nothing assumes a post-1.2 MBeanServer |
| * @author Eamonn McManus |
| * @run main/othervm -ea OldMBeanServerTest |
| */ |
| |
| /* |
| * We defined the MBeanServerBuilder class and the associated system |
| * property javax.management.builder.initial in version 1.2 of the JMX |
| * spec. That amounts to a guarantee that someone can set the property |
| * to an MBeanServer that only knows about JMX 1.2 semantics, and if they |
| * only do JMX 1.2 operations, everything should work. This test is a |
| * sanity check that ensures we don't inadvertently make any API changes |
| * that stop that from being true. It includes a complete (if slow) |
| * MBeanServer implementation. That implementation doesn't replicate the |
| * mandated exception behaviour everywhere, though, since there's lots of |
| * arbitrary cruft in that. Also, the behaviour of concurrent unregisterMBean |
| * calls is incorrect in detail. |
| */ |
| |
| public class OldMBeanServerTest { |
| private static MBeanServerConnection mbsc; |
| private static String failure; |
| |
| public static void main(String[] args) throws Exception { |
| if (!OldMBeanServerTest.class.desiredAssertionStatus()) |
| throw new Exception("Test must be run with -ea"); |
| |
| System.setProperty("javax.management.builder.initial", |
| OldMBeanServerBuilder.class.getName()); |
| assert MBeanServerFactory.newMBeanServer() instanceof OldMBeanServer; |
| |
| System.out.println("=== RUNNING TESTS WITH LOCAL MBEANSERVER ==="); |
| runTests(new Callable<MBeanServerConnection>() { |
| public MBeanServerConnection call() { |
| return MBeanServerFactory.newMBeanServer(); |
| } |
| }, null); |
| |
| System.out.println("=== RUNNING TESTS THROUGH CONNECTOR ==="); |
| ConnectionBuilder builder = new ConnectionBuilder(); |
| runTests(builder, builder); |
| |
| if (failure == null) |
| System.out.println("TEST PASSED"); |
| else |
| throw new Exception("TEST FAILED: " + failure); |
| } |
| |
| private static class ConnectionBuilder |
| implements Callable<MBeanServerConnection>, Runnable { |
| private JMXConnector connector; |
| public MBeanServerConnection call() { |
| MBeanServer mbs = MBeanServerFactory.newMBeanServer(); |
| try { |
| JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); |
| JMXConnectorServer cs = |
| JMXConnectorServerFactory.newJMXConnectorServer( |
| url, null, mbs); |
| cs.start(); |
| JMXServiceURL addr = cs.getAddress(); |
| connector = JMXConnectorFactory.connect(addr); |
| return connector.getMBeanServerConnection(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| public void run() { |
| if (connector != null) { |
| try { |
| connector.close(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| } |
| |
| private static void runTests( |
| Callable<MBeanServerConnection> maker, Runnable breaker) |
| throws Exception { |
| for (Method m : OldMBeanServerTest.class.getDeclaredMethods()) { |
| if (Modifier.isStatic(m.getModifiers()) && |
| m.getName().startsWith("test") && |
| m.getParameterTypes().length == 0) { |
| ExpectException expexc = m.getAnnotation(ExpectException.class); |
| mbsc = maker.call(); |
| try { |
| m.invoke(null); |
| if (expexc != null) { |
| failure = |
| m.getName() + " did not got expected exception " + |
| expexc.value().getName(); |
| System.out.println(failure); |
| } else |
| System.out.println(m.getName() + " OK"); |
| } catch (InvocationTargetException ite) { |
| Throwable t = ite.getCause(); |
| String prob = null; |
| if (expexc != null) { |
| if (expexc.value().isInstance(t)) { |
| System.out.println(m.getName() + " OK (got expected " + |
| expexc.value().getName() + ")"); |
| } else |
| prob = "got wrong exception"; |
| } else |
| prob = "got exception"; |
| if (prob != null) { |
| failure = m.getName() + ": " + prob + " " + |
| t.getClass().getName(); |
| System.out.println(failure); |
| t.printStackTrace(System.out); |
| } |
| } finally { |
| if (breaker != null) |
| breaker.run(); |
| } |
| } |
| } |
| } |
| |
| @Retention(RetentionPolicy.RUNTIME) |
| private static @interface ExpectException { |
| Class<? extends Exception> value(); |
| } |
| |
| public static interface BoringMBean { |
| public String getName(); |
| public int add(int x, int y); |
| } |
| |
| // This class is Serializable so we can createMBean a StandardMBean |
| // that contains it. Not recommended practice in general -- |
| // should we have a StandardMBean constructor that takes a class |
| // name and constructor parameters? |
| public static class Boring implements BoringMBean, Serializable { |
| public String getName() { |
| return "Jessica"; |
| } |
| |
| public int add(int x, int y) { |
| return x + y; |
| } |
| } |
| |
| public static interface BoringNotifierMBean extends BoringMBean { |
| public void send(); |
| } |
| |
| public static class BoringNotifier |
| extends Boring implements BoringNotifierMBean, NotificationBroadcaster { |
| private final NotificationBroadcasterSupport nbs = |
| new NotificationBroadcasterSupport(); |
| |
| public void addNotificationListener( |
| NotificationListener listener, NotificationFilter filter, Object handback) |
| throws IllegalArgumentException { |
| nbs.addNotificationListener(listener, filter, handback); |
| } |
| |
| public void removeNotificationListener(NotificationListener listener) |
| throws ListenerNotFoundException { |
| nbs.removeNotificationListener(listener); |
| } |
| |
| public MBeanNotificationInfo[] getNotificationInfo() { |
| return null; |
| } |
| |
| public void send() { |
| Notification n = new Notification("type.type", this, 0L); |
| nbs.sendNotification(n); |
| } |
| } |
| |
| private static class CountListener implements NotificationListener { |
| volatile int count; |
| public void handleNotification(Notification n, Object h) { |
| if (h == null) |
| h = 1; |
| count += (Integer) h; |
| } |
| void waitForCount(int expect) throws InterruptedException { |
| long deadline = System.currentTimeMillis() + 2000L; |
| while (count < expect && System.currentTimeMillis() < deadline) |
| Thread.sleep(1); |
| assert count == expect; |
| } |
| } |
| |
| private static void testBasic() throws Exception { |
| CountListener countListener = new CountListener(); |
| mbsc.addNotificationListener( |
| MBeanServerDelegate.DELEGATE_NAME, countListener, null, null); |
| assert countListener.count == 0; |
| ObjectName name = new ObjectName("a:b=c"); |
| if (mbsc instanceof MBeanServer) |
| ((MBeanServer) mbsc).registerMBean(new Boring(), name); |
| else |
| mbsc.createMBean(Boring.class.getName(), name); |
| countListener.waitForCount(1); |
| assert mbsc.isRegistered(name); |
| assert mbsc.queryNames(null, null).contains(name); |
| assert mbsc.getAttribute(name, "Name").equals("Jessica"); |
| assert mbsc.invoke( |
| name, "add", new Object[] {2, 3}, new String[] {"int", "int"}) |
| .equals(5); |
| mbsc.unregisterMBean(name); |
| countListener.waitForCount(2); |
| assert !mbsc.isRegistered(name); |
| assert !mbsc.queryNames(null, null).contains(name); |
| |
| mbsc.createMBean(BoringNotifier.class.getName(), name); |
| countListener.waitForCount(3); |
| CountListener boringListener = new CountListener(); |
| class AlwaysNotificationFilter implements NotificationFilter { |
| public boolean isNotificationEnabled(Notification notification) { |
| return true; |
| } |
| } |
| mbsc.addNotificationListener( |
| name, boringListener, new AlwaysNotificationFilter(), 5); |
| mbsc.invoke(name, "send", null, null); |
| boringListener.waitForCount(5); |
| } |
| |
| private static void testPrintAttrs() throws Exception { |
| printAttrs(mbsc, null); |
| } |
| |
| private static void testPlatformMBeanServer() throws Exception { |
| MBeanServer pmbs = ManagementFactory.getPlatformMBeanServer(); |
| assert pmbs instanceof OldMBeanServer; |
| // Preceding assertion could be violated if at some stage we wrap |
| // the Platform MBeanServer. In that case we can still check that |
| // it is ultimately an OldMBeanServer for example by adding a |
| // counter to getAttribute and checking that it is incremented |
| // when we call pmbs.getAttribute. |
| |
| printAttrs(pmbs, UnsupportedOperationException.class); |
| ObjectName memoryMXBeanName = |
| new ObjectName(ManagementFactory.MEMORY_MXBEAN_NAME); |
| pmbs.invoke(memoryMXBeanName, "gc", null, null); |
| } |
| |
| private static void printAttrs( |
| MBeanServerConnection mbsc1, Class<? extends Exception> expectX) |
| throws Exception { |
| Set<ObjectName> names = mbsc1.queryNames(null, null); |
| for (ObjectName name : names) { |
| System.out.println(name + ":"); |
| MBeanInfo mbi = mbsc1.getMBeanInfo(name); |
| MBeanAttributeInfo[] mbais = mbi.getAttributes(); |
| for (MBeanAttributeInfo mbai : mbais) { |
| String attr = mbai.getName(); |
| Object value; |
| try { |
| value = mbsc1.getAttribute(name, attr); |
| } catch (Exception e) { |
| if (expectX != null && expectX.isInstance(e)) |
| value = "<" + e + ">"; |
| else |
| throw e; |
| } |
| String s = " " + attr + " = " + value; |
| if (s.length() > 80) |
| s = s.substring(0, 77) + "..."; |
| System.out.println(s); |
| } |
| } |
| } |
| |
| private static void testJavaxManagementStandardMBean() throws Exception { |
| ObjectName name = new ObjectName("a:b=c"); |
| Object mbean = new StandardMBean(new Boring(), BoringMBean.class); |
| mbsc.createMBean( |
| StandardMBean.class.getName(), name, |
| new Object[] {new Boring(), BoringMBean.class}, |
| new String[] {Object.class.getName(), Class.class.getName()}); |
| assert mbsc.getAttribute(name, "Name").equals("Jessica"); |
| assert mbsc.invoke( |
| name, "add", new Object[] {2, 3}, new String[] {"int", "int"}) |
| .equals(5); |
| mbsc.unregisterMBean(name); |
| } |
| |
| private static void testConnector() throws Exception { |
| } |
| |
| public static class OldMBeanServerBuilder extends MBeanServerBuilder { |
| public MBeanServer newMBeanServer( |
| String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) { |
| return new OldMBeanServer(defaultDomain, delegate); |
| } |
| } |
| |
| public static class OldMBeanServer implements MBeanServer { |
| // We pretend there's a ClassLoader MBean representing the Class Loader |
| // Repository and intercept references to it where necessary to keep up |
| // the pretence. This allows us to fake the right behaviour for |
| // the omitted-ClassLoader versions of createMBean and instantiate |
| // (which are not the same as passing a null for the ClassLoader parameter |
| // of the versions that have one). |
| private static final ObjectName clrName; |
| static { |
| try { |
| clrName = |
| new ObjectName("JMImplementation:type=ClassLoaderRepository"); |
| } catch (MalformedObjectNameException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private final ConcurrentMap<ObjectName, DynamicMBean> mbeans = |
| new ConcurrentHashMap<ObjectName, DynamicMBean>(); |
| private final ConcurrentMap<ObjectName, ListenerTable> listenerMap = |
| new ConcurrentHashMap<ObjectName, ListenerTable>(); |
| private final String defaultDomain; |
| private final MBeanServerDelegate delegate; |
| private final ClassLoaderRepositoryImpl clr = |
| new ClassLoaderRepositoryImpl(); |
| |
| OldMBeanServer(String defaultDomain, MBeanServerDelegate delegate) { |
| this.defaultDomain = defaultDomain; |
| this.delegate = delegate; |
| try { |
| registerMBean(delegate, MBeanServerDelegate.DELEGATE_NAME); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public ObjectInstance createMBean(String className, ObjectName name) |
| throws ReflectionException, InstanceAlreadyExistsException, |
| MBeanRegistrationException, MBeanException, |
| NotCompliantMBeanException { |
| return createMBean(className, name, null, null); |
| } |
| |
| public ObjectInstance createMBean( |
| String className, ObjectName name, ObjectName loaderName) |
| throws ReflectionException, InstanceAlreadyExistsException, |
| MBeanRegistrationException, MBeanException, |
| NotCompliantMBeanException, InstanceNotFoundException { |
| return createMBean(className, name, loaderName, null, null); |
| } |
| |
| public ObjectInstance createMBean( |
| String className, ObjectName name, Object[] params, String[] signature) |
| throws ReflectionException, InstanceAlreadyExistsException, |
| MBeanRegistrationException, MBeanException, |
| NotCompliantMBeanException { |
| try { |
| return createMBean(className, name, clrName, params, signature); |
| } catch (InstanceNotFoundException ex) { |
| throw new RuntimeException(ex); // can't happen |
| } |
| } |
| |
| public ObjectInstance createMBean( |
| String className, ObjectName name, ObjectName loaderName, |
| Object[] params, String[] signature) |
| throws ReflectionException, InstanceAlreadyExistsException, |
| MBeanRegistrationException, MBeanException, |
| NotCompliantMBeanException, InstanceNotFoundException { |
| Object mbean = instantiate(className, loaderName, params, signature); |
| return registerMBean(mbean, name); |
| } |
| |
| private void forbidJMImpl(ObjectName name) { |
| if (name.getDomain().equals("JMImplementation") && |
| mbeans.containsKey(MBeanServerDelegate.DELEGATE_NAME)) |
| throw new IllegalArgumentException("JMImplementation reserved"); |
| } |
| |
| public ObjectInstance registerMBean(Object object, ObjectName name) |
| throws InstanceAlreadyExistsException, MBeanRegistrationException, |
| NotCompliantMBeanException { |
| forbidJMImpl(name); |
| if (name.isPattern()) |
| throw new IllegalArgumentException(name.toString()); |
| // This is the only place we check for wildcards. Since you |
| // can't register a wildcard name, other operations that supply |
| // one will get InstanceNotFoundException when they look it up. |
| |
| DynamicMBean mbean; |
| if (object instanceof DynamicMBean) |
| mbean = (DynamicMBean) object; |
| else |
| mbean = standardToDynamic(object); |
| MBeanRegistration reg = mbeanRegistration(object); |
| try { |
| name = reg.preRegister(this, name); |
| } catch (Exception e) { |
| throw new MBeanRegistrationException(e); |
| } |
| DynamicMBean put = mbeans.putIfAbsent(name, mbean); |
| if (put != null) { |
| reg.postRegister(false); |
| throw new InstanceAlreadyExistsException(name.toString()); |
| } |
| reg.postRegister(true); |
| |
| if (object instanceof ClassLoader) |
| clr.addLoader((ClassLoader) object); |
| |
| Notification n = new MBeanServerNotification( |
| MBeanServerNotification.REGISTRATION_NOTIFICATION, |
| MBeanServerDelegate.DELEGATE_NAME, |
| 0, |
| name); |
| delegate.sendNotification(n); |
| |
| String className = mbean.getMBeanInfo().getClassName(); |
| return new ObjectInstance(name, className); |
| } |
| |
| public void unregisterMBean(ObjectName name) |
| throws InstanceNotFoundException, MBeanRegistrationException { |
| |
| forbidJMImpl(name); |
| |
| DynamicMBean mbean = getMBean(name); |
| if (mbean == null) |
| throw new InstanceNotFoundException(name.toString()); |
| |
| MBeanRegistration reg = mbeanRegistration(mbean); |
| try { |
| reg.preDeregister(); |
| } catch (Exception e) { |
| throw new MBeanRegistrationException(e); |
| } |
| if (!mbeans.remove(name, mbean)) |
| throw new InstanceNotFoundException(name.toString()); |
| // This is incorrect because we've invoked preDeregister |
| |
| Object userMBean = getUserMBean(mbean); |
| if (userMBean instanceof ClassLoader) |
| clr.removeLoader((ClassLoader) userMBean); |
| |
| Notification n = new MBeanServerNotification( |
| MBeanServerNotification.REGISTRATION_NOTIFICATION, |
| MBeanServerDelegate.DELEGATE_NAME, |
| 0, |
| name); |
| delegate.sendNotification(n); |
| |
| reg.postDeregister(); |
| } |
| |
| public ObjectInstance getObjectInstance(ObjectName name) |
| throws InstanceNotFoundException { |
| DynamicMBean mbean = getMBean(name); |
| return new ObjectInstance(name, mbean.getMBeanInfo().getClassName()); |
| } |
| |
| private static class TrueQueryExp implements QueryExp { |
| public boolean apply(ObjectName name) { |
| return true; |
| } |
| |
| public void setMBeanServer(MBeanServer s) {} |
| } |
| private static final QueryExp trueQuery = new TrueQueryExp(); |
| |
| public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) { |
| Set<ObjectInstance> instances = newSet(); |
| if (name == null) |
| name = ObjectName.WILDCARD; |
| if (query == null) |
| query = trueQuery; |
| MBeanServer oldMBS = QueryEval.getMBeanServer(); |
| try { |
| query.setMBeanServer(this); |
| for (ObjectName n : mbeans.keySet()) { |
| if (name.apply(n)) { |
| try { |
| if (query.apply(n)) |
| instances.add(getObjectInstance(n)); |
| } catch (Exception e) { |
| // OK: Ignore this MBean in the result |
| } |
| } |
| } |
| } finally { |
| query.setMBeanServer(oldMBS); |
| } |
| return instances; |
| } |
| |
| public Set<ObjectName> queryNames(ObjectName name, QueryExp query) { |
| Set<ObjectInstance> instances = queryMBeans(name, query); |
| Set<ObjectName> names = newSet(); |
| for (ObjectInstance instance : instances) |
| names.add(instance.getObjectName()); |
| return names; |
| } |
| |
| public boolean isRegistered(ObjectName name) { |
| return mbeans.containsKey(name); |
| } |
| |
| public Integer getMBeanCount() { |
| return mbeans.size(); |
| } |
| |
| public Object getAttribute(ObjectName name, String attribute) |
| throws MBeanException, AttributeNotFoundException, |
| InstanceNotFoundException, ReflectionException { |
| return getMBean(name).getAttribute(attribute); |
| } |
| |
| public AttributeList getAttributes(ObjectName name, String[] attributes) |
| throws InstanceNotFoundException, ReflectionException { |
| return getMBean(name).getAttributes(attributes); |
| } |
| |
| public void setAttribute(ObjectName name, Attribute attribute) |
| throws InstanceNotFoundException, AttributeNotFoundException, |
| InvalidAttributeValueException, MBeanException, |
| ReflectionException { |
| getMBean(name).setAttribute(attribute); |
| } |
| |
| public AttributeList setAttributes( |
| ObjectName name, AttributeList attributes) |
| throws InstanceNotFoundException, ReflectionException { |
| return getMBean(name).setAttributes(attributes); |
| } |
| |
| public Object invoke( |
| ObjectName name, String operationName, Object[] params, |
| String[] signature) |
| throws InstanceNotFoundException, MBeanException, ReflectionException { |
| return getMBean(name).invoke(operationName, params, signature); |
| } |
| |
| public String getDefaultDomain() { |
| return defaultDomain; |
| } |
| |
| public String[] getDomains() { |
| Set<String> domains = newSet(); |
| for (ObjectName name : mbeans.keySet()) |
| domains.add(name.getDomain()); |
| return domains.toArray(new String[0]); |
| } |
| |
| // ClassCastException if MBean is not a NotificationBroadcaster |
| public void addNotificationListener( |
| ObjectName name, NotificationListener listener, |
| NotificationFilter filter, Object handback) |
| throws InstanceNotFoundException { |
| NotificationBroadcaster userMBean = |
| (NotificationBroadcaster) getUserMBean(name); |
| NotificationListener wrappedListener = |
| wrappedListener(name, userMBean, listener); |
| userMBean.addNotificationListener(wrappedListener, filter, handback); |
| } |
| |
| public void addNotificationListener( |
| ObjectName name, ObjectName listener, |
| NotificationFilter filter, Object handback) |
| throws InstanceNotFoundException { |
| NotificationListener nl = |
| (NotificationListener) getUserMBean(listener); |
| addNotificationListener(name, nl, filter, handback); |
| } |
| |
| public void removeNotificationListener( |
| ObjectName name, ObjectName listener) |
| throws InstanceNotFoundException, ListenerNotFoundException { |
| NotificationListener nl = |
| (NotificationListener) getUserMBean(listener); |
| removeNotificationListener(name, nl); |
| } |
| |
| public void removeNotificationListener( |
| ObjectName name, ObjectName listener, |
| NotificationFilter filter, Object handback) |
| throws InstanceNotFoundException, ListenerNotFoundException { |
| NotificationListener nl = |
| (NotificationListener) getUserMBean(listener); |
| removeNotificationListener(name, nl, filter, handback); |
| } |
| |
| public void removeNotificationListener( |
| ObjectName name, NotificationListener listener) |
| throws InstanceNotFoundException, ListenerNotFoundException { |
| NotificationBroadcaster userMBean = |
| (NotificationBroadcaster) getUserMBean(name); |
| NotificationListener wrappedListener = |
| wrappedListener(name, userMBean, listener); |
| userMBean.removeNotificationListener(wrappedListener); |
| } |
| |
| public void removeNotificationListener( |
| ObjectName name, NotificationListener listener, |
| NotificationFilter filter, Object handback) |
| throws InstanceNotFoundException, ListenerNotFoundException { |
| NotificationEmitter userMBean = |
| (NotificationEmitter) getMBean(name); |
| NotificationListener wrappedListener = |
| wrappedListener(name, userMBean, listener); |
| userMBean.removeNotificationListener(wrappedListener, filter, handback); |
| } |
| |
| public MBeanInfo getMBeanInfo(ObjectName name) |
| throws InstanceNotFoundException, IntrospectionException, |
| ReflectionException { |
| return getMBean(name).getMBeanInfo(); |
| } |
| |
| public boolean isInstanceOf(ObjectName name, String className) |
| throws InstanceNotFoundException { |
| DynamicMBean mbean = getMBean(name); |
| String mbeanClassName = mbean.getMBeanInfo().getClassName(); |
| if (className.equals(mbeanClassName)) |
| return true; |
| ClassLoader loader = getUserMBean(mbean).getClass().getClassLoader(); |
| try { |
| Class<?> mbeanClass = Class.forName(mbeanClassName, false, loader); |
| Class<?> isInstClass = Class.forName(className, false, loader); |
| return isInstClass.isAssignableFrom(mbeanClass); |
| } catch (ClassNotFoundException e) { |
| return false; |
| } |
| } |
| |
| public Object instantiate(String className) |
| throws ReflectionException, MBeanException { |
| return instantiate(className, null, null); |
| } |
| |
| public Object instantiate(String className, ObjectName loaderName) |
| throws ReflectionException, MBeanException, InstanceNotFoundException { |
| return instantiate(className, loaderName, null, null); |
| } |
| |
| public Object instantiate( |
| String className, Object[] params, String[] signature) |
| throws ReflectionException, MBeanException { |
| try { |
| return instantiate(className, clrName, params, signature); |
| } catch (InstanceNotFoundException e) { |
| throw new RuntimeException(e); // can't happen |
| } |
| } |
| |
| public Object instantiate( |
| String className, ObjectName loaderName, |
| Object[] params, String[] signature) |
| throws ReflectionException, MBeanException, InstanceNotFoundException { |
| |
| if (params == null) |
| params = new Object[0]; |
| if (signature == null) |
| signature = new String[0]; |
| |
| ClassLoader loader; |
| if (loaderName == null) |
| loader = this.getClass().getClassLoader(); |
| else if (loaderName.equals(clrName)) |
| loader = clr; |
| else |
| loader = (ClassLoader) getMBean(loaderName); |
| |
| Class<?> c; |
| try { |
| c = Class.forName(className, false, loader); |
| } catch (ClassNotFoundException e) { |
| throw new ReflectionException(e); |
| } |
| |
| Constructor[] constrs = c.getConstructors(); |
| Constructor found = null; |
| findconstr: |
| for (Constructor constr : constrs) { |
| Class<?>[] cTypes = constr.getParameterTypes(); |
| if (cTypes.length == signature.length) { |
| for (int i = 0; i < cTypes.length; i++) { |
| if (!cTypes[i].getName().equals(signature[i])) |
| continue findconstr; |
| } |
| found = constr; |
| break findconstr; |
| } |
| } |
| if (found == null) { |
| Exception x = new NoSuchMethodException( |
| className + Arrays.toString(signature)); |
| throw new ReflectionException(x); |
| } |
| return invokeSomething(found, null, params); |
| } |
| |
| @Deprecated |
| public ObjectInputStream deserialize(ObjectName name, byte[] data) |
| throws InstanceNotFoundException, OperationsException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public ObjectInputStream deserialize(String className, byte[] data) |
| throws OperationsException, ReflectionException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public ObjectInputStream deserialize( |
| String className, ObjectName loaderName, byte[] data) |
| throws InstanceNotFoundException, OperationsException, ReflectionException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public ClassLoader getClassLoaderFor(ObjectName mbeanName) |
| throws InstanceNotFoundException { |
| DynamicMBean mbean = getMBean(mbeanName); |
| Object userMBean = getUserMBean(mbean); |
| return userMBean.getClass().getClassLoader(); |
| } |
| |
| public ClassLoader getClassLoader(ObjectName loaderName) |
| throws InstanceNotFoundException { |
| return (ClassLoader) getMBean(loaderName); |
| } |
| |
| public ClassLoaderRepository getClassLoaderRepository() { |
| return new ClassLoaderRepository() { |
| public Class<?> loadClass(String className) |
| throws ClassNotFoundException { |
| return clr.loadClass(className); |
| } |
| |
| public Class<?> loadClassWithout( |
| ClassLoader exclude, String className) |
| throws ClassNotFoundException { |
| return clr.loadClassWithout(exclude, className); |
| } |
| |
| public Class<?> loadClassBefore( |
| ClassLoader stop, String className) |
| throws ClassNotFoundException { |
| return clr.loadClassBefore(stop, className); |
| } |
| }; |
| } |
| |
| private static class ClassLoaderRepositoryImpl |
| extends ClassLoader implements ClassLoaderRepository { |
| private List<ClassLoader> loaders = newList(); |
| { |
| loaders.add(this.getClass().getClassLoader()); |
| // We also behave as if the system class loader were in |
| // the repository, since we do nothing to stop delegation |
| // to the parent, which is the system class loader, and |
| // that delegation happens before our findClass is called. |
| } |
| |
| void addLoader(ClassLoader loader) { |
| loaders.add(loader); |
| } |
| |
| void removeLoader(ClassLoader loader) { |
| if (!loaders.remove(loader)) |
| throw new RuntimeException("Loader was not in CLR!"); |
| } |
| |
| public Class<?> loadClassWithout( |
| ClassLoader exclude, String className) |
| throws ClassNotFoundException { |
| return loadClassWithoutBefore(exclude, null, className); |
| } |
| |
| public Class<?> loadClassBefore(ClassLoader stop, String className) |
| throws ClassNotFoundException { |
| return loadClassWithoutBefore(null, stop, className); |
| } |
| |
| private Class<?> loadClassWithoutBefore( |
| ClassLoader exclude, ClassLoader stop, String className) |
| throws ClassNotFoundException { |
| for (ClassLoader loader : loaders) { |
| if (loader == exclude) |
| continue; |
| if (loader == stop) |
| break; |
| try { |
| return Class.forName(className, false, loader); |
| } catch (ClassNotFoundException e) { |
| // OK: try others |
| } |
| } |
| throw new ClassNotFoundException(className); |
| } |
| |
| @Override |
| protected Class<?> findClass(String className) |
| throws ClassNotFoundException { |
| return loadClassWithout(null, className); |
| } |
| } |
| |
| /* There is zero or one ListenerTable per MBean. |
| * The ListenerTable stuff is complicated. We want to rewrite the |
| * source of notifications so that if the source of a notification |
| * from the MBean X is a reference to X itself, it gets replaced |
| * by X's ObjectName. To do this, we wrap the user's listener in |
| * a RewriteListener. But if the same listener is added a second |
| * time (perhaps with a different filter or handback) we must |
| * reuse the same RewriteListener so that the two-argument |
| * removeNotificationListener(ObjectName,NotificationListener) will |
| * correctly remove both listeners. This means we must remember the |
| * mapping from listener to WrappedListener. But if the MBean |
| * discards its listeners (as a result of removeNL or spontaneously) |
| * then we don't want to keep a reference to the WrappedListener. |
| * So we have tons of WeakReferences. The key in the ListenerTable |
| * is an IdentityListener, which wraps the user's listener to ensure |
| * that identity and not equality is used during the lookup, even if |
| * the user's listener has an equals method. The value in the |
| * ListenerTable is a WeakReference wrapping a RewriteListener wrapping |
| * the same IdentityListener. Since the RewriteListener is what is |
| * added to the user's MBean, the WeakReference won't disappear as long |
| * as the MBean still has this listener. And since it references the |
| * IdentityListener, that won't disappear either. But once the |
| * RewriteListener is no longer referenced by the user's MBean, |
| * there's nothing to stop its WeakReference from being cleared, |
| * and then corresponding IdentityListener that is now only weakly |
| * referenced from the key in the table. |
| */ |
| private static class ListenerTable |
| extends WeakHashMap<NotificationListener, |
| WeakReference<NotificationListener>> { |
| } |
| |
| private static class IdentityListener implements NotificationListener { |
| private final NotificationListener userListener; |
| |
| IdentityListener(NotificationListener userListener) { |
| this.userListener = userListener; |
| } |
| |
| public void handleNotification( |
| Notification notification, Object handback) { |
| userListener.handleNotification(notification, handback); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| return (this == o); |
| } |
| |
| @Override |
| public int hashCode() { |
| return System.identityHashCode(this); |
| } |
| } |
| |
| private static class RewriteListener implements NotificationListener { |
| private final ObjectName name; |
| private final Object userMBean; |
| private final NotificationListener userListener; |
| |
| RewriteListener( |
| ObjectName name, Object userMBean, |
| NotificationListener userListener) { |
| this.name = name; |
| this.userMBean = userMBean; |
| this.userListener = userListener; |
| } |
| |
| public void handleNotification( |
| Notification notification, Object handback) { |
| if (notification.getSource() == userMBean) |
| notification.setSource(name); |
| userListener.handleNotification(notification, handback); |
| } |
| } |
| |
| private NotificationListener wrappedListener( |
| ObjectName name, Object userMBean, NotificationListener userListener) |
| throws InstanceNotFoundException { |
| ListenerTable table = new ListenerTable(); |
| ListenerTable oldTable = listenerMap.putIfAbsent(name, table); |
| if (oldTable != null) |
| table = oldTable; |
| NotificationListener identityListener = |
| new IdentityListener(userListener); |
| synchronized (table) { |
| NotificationListener rewriteListener = null; |
| WeakReference<NotificationListener> wr = |
| table.get(identityListener); |
| if (wr != null) |
| rewriteListener = wr.get(); |
| if (rewriteListener == null) { |
| rewriteListener = new RewriteListener( |
| name, userMBean, identityListener); |
| wr = new WeakReference<NotificationListener>(rewriteListener); |
| table.put(identityListener, wr); |
| } |
| return rewriteListener; |
| } |
| } |
| |
| private DynamicMBean getMBean(ObjectName name) |
| throws InstanceNotFoundException { |
| DynamicMBean mbean = mbeans.get(name); |
| if (mbean == null) |
| throw new InstanceNotFoundException(name.toString()); |
| return mbean; |
| } |
| |
| private static interface WrapDynamicMBean extends DynamicMBean { |
| public Object getWrappedMBean(); |
| } |
| |
| private static class StandardWrapper |
| implements WrapDynamicMBean, MBeanRegistration { |
| private final Map<String, AttrMethods> attrMap = newMap(); |
| private final Map<String, List<Method>> opMap = newMap(); |
| private static class AttrMethods { |
| Method getter, setter; |
| } |
| |
| private final Object std; |
| |
| StandardWrapper(Object std) throws NotCompliantMBeanException { |
| this.std = std; |
| Class<?> intf = mbeanInterface(std.getClass()); |
| try { |
| initMaps(intf); |
| } catch (NotCompliantMBeanException e) { |
| throw e; |
| } catch (Exception e) { |
| NotCompliantMBeanException x = |
| new NotCompliantMBeanException(e.getMessage()); |
| x.initCause(e); |
| throw x; |
| } |
| } |
| |
| private static Class<?> mbeanInterface(Class<?> c) |
| throws NotCompliantMBeanException { |
| do { |
| Class<?>[] intfs = c.getInterfaces(); |
| String intfName = c.getName() + "MBean"; |
| for (Class<?> intf : intfs) { |
| if (intf.getName().equals(intfName)) |
| return intf; |
| } |
| c = c.getSuperclass(); |
| } while (c != null); |
| throw new NotCompliantMBeanException( |
| "Does not match Standard or Dynamic MBean patterns: " + |
| c.getName()); |
| } |
| |
| private void initMaps(Class<?> intf) throws NotCompliantMBeanException { |
| Method[] methods = intf.getMethods(); |
| |
| for (Method m : methods) { |
| final String name = m.getName(); |
| final int nParams = m.getParameterTypes().length; |
| |
| String attrName = ""; |
| if (name.startsWith("get")) |
| attrName = name.substring(3); |
| else if (name.startsWith("is") |
| && m.getReturnType() == boolean.class) |
| attrName = name.substring(2); |
| |
| if (attrName.length() != 0 && m.getParameterTypes().length == 0 |
| && m.getReturnType() != void.class) { |
| // It's a getter |
| // Check we don't have both isX and getX |
| AttrMethods am = attrMap.get(attrName); |
| if (am == null) |
| am = new AttrMethods(); |
| else { |
| if (am.getter != null) { |
| final String msg = "Attribute " + attrName + |
| " has more than one getter"; |
| throw new NotCompliantMBeanException(msg); |
| } |
| } |
| am.getter = m; |
| attrMap.put(attrName, am); |
| } else if (name.startsWith("set") && name.length() > 3 |
| && m.getParameterTypes().length == 1 && |
| m.getReturnType() == void.class) { |
| // It's a setter |
| attrName = name.substring(3); |
| AttrMethods am = attrMap.get(attrName); |
| if (am == null) |
| am = new AttrMethods(); |
| else if (am.setter != null) { |
| final String msg = "Attribute " + attrName + |
| " has more than one setter"; |
| throw new NotCompliantMBeanException(msg); |
| } |
| am.setter = m; |
| attrMap.put(attrName, am); |
| } else { |
| // It's an operation |
| List<Method> ops = opMap.get(name); |
| if (ops == null) |
| ops = newList(); |
| ops.add(m); |
| opMap.put(name, ops); |
| } |
| } |
| /* Check that getters and setters are consistent. */ |
| for (Map.Entry<String, AttrMethods> entry : attrMap.entrySet()) { |
| AttrMethods am = entry.getValue(); |
| if (am.getter != null && am.setter != null && |
| am.getter.getReturnType() != am.setter.getParameterTypes()[0]) { |
| final String msg = "Getter and setter for " + entry.getKey() + |
| " have inconsistent types"; |
| throw new NotCompliantMBeanException(msg); |
| } |
| } |
| } |
| |
| public Object getAttribute(String attribute) |
| throws AttributeNotFoundException, MBeanException, ReflectionException { |
| AttrMethods am = attrMap.get(attribute); |
| if (am == null || am.getter == null) |
| throw new AttributeNotFoundException(attribute); |
| return invokeMethod(am.getter); |
| } |
| |
| public void setAttribute(Attribute attribute) |
| throws AttributeNotFoundException, InvalidAttributeValueException, |
| MBeanException, ReflectionException { |
| String name = attribute.getName(); |
| AttrMethods am = attrMap.get(name); |
| if (am == null || am.setter == null) |
| throw new AttributeNotFoundException(name); |
| invokeMethod(am.setter, attribute.getValue()); |
| } |
| |
| public AttributeList getAttributes(String[] attributes) { |
| AttributeList list = new AttributeList(); |
| for (String attr : attributes) { |
| try { |
| list.add(new Attribute(attr, getAttribute(attr))); |
| } catch (Exception e) { |
| // OK: ignore per spec |
| } |
| } |
| return list; |
| } |
| |
| public AttributeList setAttributes(AttributeList attributes) { |
| AttributeList list = new AttributeList(); |
| // We carefully avoid using any new stuff from AttributeList here! |
| for (Iterator<?> it = attributes.iterator(); it.hasNext(); ) { |
| Attribute attr = (Attribute) it.next(); |
| try { |
| setAttribute(attr); |
| list.add(attr); |
| } catch (Exception e) { |
| // OK: ignore per spec |
| } |
| } |
| return list; |
| } |
| |
| public Object invoke(String actionName, Object[] params, String[] signature) |
| throws MBeanException, ReflectionException { |
| if (params == null) |
| params = new Object[0]; |
| if (signature == null) |
| signature = new String[0]; |
| List<Method> methods = opMap.get(actionName); |
| if (methods == null) { |
| Exception x = new NoSuchMethodException(actionName); |
| throw new MBeanException(x); |
| } |
| Method found = null; |
| methodloop: |
| for (Method m : methods) { |
| Class<?>[] msig = m.getParameterTypes(); |
| if (msig.length != signature.length) |
| continue methodloop; |
| for (int i = 0; i < msig.length; i++) { |
| if (!msig[i].getName().equals(signature[i])) |
| continue methodloop; |
| } |
| found = m; |
| break methodloop; |
| } |
| if (found == null) { |
| Exception x = new NoSuchMethodException( |
| actionName + Arrays.toString(signature)); |
| throw new MBeanException(x); |
| } |
| return invokeMethod(found, params); |
| } |
| |
| public MBeanInfo getMBeanInfo() { |
| // Attributes |
| List<MBeanAttributeInfo> attrs = newList(); |
| for (Map.Entry<String, AttrMethods> attr : attrMap.entrySet()) { |
| String name = attr.getKey(); |
| AttrMethods am = attr.getValue(); |
| try { |
| attrs.add(new MBeanAttributeInfo( |
| name, name, am.getter, am.setter)); |
| } catch (IntrospectionException e) { // grrr |
| throw new RuntimeException(e); |
| } |
| } |
| |
| // Operations |
| List<MBeanOperationInfo> ops = newList(); |
| for (Map.Entry<String, List<Method>> op : opMap.entrySet()) { |
| String name = op.getKey(); |
| List<Method> methods = op.getValue(); |
| for (Method m : methods) |
| ops.add(new MBeanOperationInfo(name, m)); |
| } |
| |
| // Constructors |
| List<MBeanConstructorInfo> constrs = newList(); |
| for (Constructor constr : std.getClass().getConstructors()) |
| constrs.add(new MBeanConstructorInfo("Constructor", constr)); |
| |
| // Notifications |
| MBeanNotificationInfo[] notifs; |
| if (std instanceof NotificationBroadcaster) |
| notifs = ((NotificationBroadcaster) std).getNotificationInfo(); |
| else |
| notifs = null; |
| |
| String className = std.getClass().getName(); |
| return new MBeanInfo( |
| className, className, |
| attrs.toArray(new MBeanAttributeInfo[0]), |
| constrs.toArray(new MBeanConstructorInfo[0]), |
| ops.toArray(new MBeanOperationInfo[0]), |
| notifs); |
| } |
| |
| private Object invokeMethod(Method m, Object... args) |
| throws MBeanException, ReflectionException { |
| return invokeSomething(m, std,args); |
| } |
| |
| public ObjectName preRegister(MBeanServer server, ObjectName name) |
| throws Exception { |
| return mbeanRegistration(std).preRegister(server, name); |
| } |
| |
| public void postRegister(Boolean registrationDone) { |
| mbeanRegistration(std).postRegister(registrationDone); |
| } |
| |
| public void preDeregister() throws Exception { |
| mbeanRegistration(std).preDeregister(); |
| } |
| |
| public void postDeregister() { |
| mbeanRegistration(std).postDeregister(); |
| } |
| |
| public Object getWrappedMBean() { |
| return std; |
| } |
| } |
| |
| private DynamicMBean standardToDynamic(Object std) |
| throws NotCompliantMBeanException { |
| return new StandardWrapper(std); |
| } |
| |
| // private static class NotifWrapper |
| // implements WrapDynamicMBean, NotificationEmitter { |
| // private final DynamicMBean mbean; |
| // |
| // NotifWrapper(DynamicMBean mbean) { |
| // this.mbean = mbean; |
| // } |
| // |
| // public Object getAttribute(String attribute) |
| // throws AttributeNotFoundException, MBeanException, ReflectionException { |
| // return mbean.getAttribute(attribute); |
| // } |
| // |
| // public void setAttribute(Attribute attribute) |
| // throws AttributeNotFoundException, InvalidAttributeValueException, |
| // MBeanException, ReflectionException { |
| // mbean.setAttribute(attribute); |
| // } |
| // |
| // public AttributeList getAttributes(String[] attributes) { |
| // return mbean.getAttributes(attributes); |
| // } |
| // |
| // public AttributeList setAttributes(AttributeList attributes) { |
| // return mbean.setAttributes(attributes); |
| // } |
| // |
| // public Object invoke( |
| // String actionName, Object[] params, String[] signature) |
| // throws MBeanException, ReflectionException { |
| // return mbean.invoke(actionName, params, signature); |
| // } |
| // |
| // public MBeanInfo getMBeanInfo() { |
| // return mbean.getMBeanInfo(); |
| // } |
| // |
| // public void removeNotificationListener( |
| // NotificationListener listener, NotificationFilter filter, Object handback) |
| // throws ListenerNotFoundException { |
| // ((NotificationEmitter) mbean).removeNotificationListener( |
| // listener, filter, handback); |
| // // ClassCastException if MBean is not an emitter |
| // } |
| // |
| // public void addNotificationListener( |
| // NotificationListener listener, NotificationFilter filter, Object handback) |
| // throws IllegalArgumentException { |
| // ((NotificationBroadcaster) mbean).addNotificationListener( |
| // listener, filter, handback); |
| // } |
| // |
| // public void removeNotificationListener(NotificationListener listener) |
| // throws ListenerNotFoundException { |
| // ((NotificationBroadcaster) mbean).removeNotificationListener(listener); |
| // } |
| // |
| // public MBeanNotificationInfo[] getNotificationInfo() { |
| // return ((NotificationBroadcaster) mbean).getNotificationInfo(); |
| // } |
| // |
| // public Object getWrappedMBean() { |
| // return getUserMBean(mbean); |
| // } |
| // } |
| |
| private static Object invokeSomething( |
| AccessibleObject ao, Object target, Object[] args) |
| throws MBeanException, ReflectionException { |
| try { |
| if (ao instanceof Method) |
| return ((Method) ao).invoke(target, args); |
| else |
| return ((Constructor) ao).newInstance(args); |
| } catch (InvocationTargetException e) { |
| try { |
| throw e.getCause(); |
| } catch (RuntimeException x) { |
| throw new RuntimeMBeanException(x); |
| } catch (Error x) { |
| throw new RuntimeErrorException(x); |
| } catch (Exception x) { |
| throw new MBeanException(x); |
| } catch (Throwable x) { |
| throw new RuntimeException(x); // neither Error nor Exception! |
| } |
| } catch (Exception e) { |
| throw new ReflectionException(e); |
| } |
| } |
| |
| private static Object getUserMBean(DynamicMBean mbean) { |
| if (mbean instanceof WrapDynamicMBean) |
| return ((WrapDynamicMBean) mbean).getWrappedMBean(); |
| return mbean; |
| } |
| |
| private Object getUserMBean(ObjectName name) |
| throws InstanceNotFoundException { |
| return getUserMBean(getMBean(name)); |
| } |
| |
| private static final MBeanRegistration noRegistration = |
| new MBeanRegistration() { |
| public ObjectName preRegister(MBeanServer server, ObjectName name) { |
| return name; |
| } |
| |
| public void postRegister(Boolean registrationDone) { |
| } |
| |
| public void preDeregister() throws Exception { |
| } |
| |
| public void postDeregister() { |
| } |
| }; |
| |
| private static MBeanRegistration mbeanRegistration(Object object) { |
| if (object instanceof MBeanRegistration) |
| return (MBeanRegistration) object; |
| else |
| return noRegistration; |
| } |
| |
| private static <E> List<E> newList() { |
| return new ArrayList<E>(); |
| } |
| |
| private static <K, V> Map<K, V> newMap() { |
| return new HashMap<K, V>(); |
| } |
| |
| private static <E> Set<E> newSet() { |
| return new HashSet<E>(); |
| } |
| } |
| } |