| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.jexl2; |
| |
| import java.io.File; |
| import java.lang.ref.Reference; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.List; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * Basic check on automated class creation |
| */ |
| public class ClassCreatorTest extends JexlTestCase { |
| static final Log logger = LogFactory.getLog(JexlTestCase.class); |
| static final int LOOPS = 8; |
| private File base = null; |
| private JexlEngine jexl = null; |
| |
| @Override |
| public void setUp() throws Exception { |
| base = new File(System.getProperty("java.io.tmpdir") + File.pathSeparator + "jexl" + System.currentTimeMillis()); |
| jexl = new JexlEngine(); |
| jexl.setCache(512); |
| |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| deleteDirectory(base); |
| } |
| |
| private void deleteDirectory(File dir) { |
| if (dir.isDirectory()) { |
| for (File file : dir.listFiles()) { |
| if (file.isFile()) { |
| file.delete(); |
| } |
| } |
| } |
| dir.delete(); |
| } |
| |
| // A space hog class |
| static final int MEGA = 1024 * 1024; |
| public class BigObject { |
| @SuppressWarnings("unused") |
| private final byte[] space = new byte[MEGA]; |
| private final int id; |
| |
| public BigObject(int id) { |
| this.id = id; |
| } |
| |
| public int getId() { |
| return id; |
| } |
| } |
| |
| // A soft reference on class |
| static final class ClassReference extends WeakReference<Class<?>> { |
| ClassReference(Class<?> clazz, ReferenceQueue<Object> queue) { |
| super(clazz, queue); |
| } |
| } |
| // A weak reference on instance |
| static final class InstanceReference extends SoftReference<Object> { |
| InstanceReference(Object obj, ReferenceQueue<Object> queue) { |
| super(obj, queue); |
| } |
| } |
| |
| public void testOne() throws Exception { |
| // abort test if class creator can not run |
| if (!ClassCreator.canRun) { |
| return; |
| } |
| ClassCreator cctor = new ClassCreator(jexl, base); |
| cctor.setSeed(1); |
| Class<?> foo1 = cctor.createClass(); |
| assertEquals("foo1", foo1.getSimpleName()); |
| cctor.clear(); |
| } |
| |
| public void testMany() throws Exception { |
| // abort test if class creator can not run |
| if (!ClassCreator.canRun) { |
| return; |
| } |
| int pass = 0; |
| int gced = -1; |
| ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); |
| List<Reference<?>> stuff = new ArrayList<Reference<?>>(); |
| // keeping a reference on methods prevent classes from being GCed |
| // List<Object> mm = new ArrayList<Object>(); |
| Expression expr = jexl.createExpression("foo.value"); |
| Expression newx = jexl.createExpression("foo = new(clazz)"); |
| JexlContext context = new MapContext(); |
| |
| ClassCreator cctor = new ClassCreator(jexl, base); |
| for (int i = 0; i < LOOPS && gced < 0; ++i) { |
| cctor.setSeed(i); |
| Class<?> clazz; |
| if (pass == 0) { |
| clazz = cctor.createClass(); |
| } else { |
| clazz = cctor.getClassInstance(); |
| if (clazz == null) { |
| assertEquals(i, gced); |
| break; |
| } |
| } |
| // this code verifies the assumption that holding a strong reference to a method prevents |
| // its owning class from being GCed |
| // Method m = clazz.getDeclaredMethod("getValue", new Class<?>[0]); |
| // mm.add(m); |
| // we should not be able to create foox since it is unknown to the Jexl classloader |
| context.set("clazz", cctor.getClassName()); |
| context.set("foo", null); |
| Object z = newx.evaluate(context); |
| assertNull(z); |
| // check with the class itself |
| context.set("clazz", clazz); |
| z = newx.evaluate(context); |
| assertNotNull(clazz + ": class " + i + " could not be instantiated on pass " + pass, z); |
| assertEquals(new Integer(i), expr.evaluate(context)); |
| // with the proper class loader, attempt to create an instance from the class name |
| jexl.setClassLoader(cctor.getClassLoader()); |
| z = newx.evaluate(context); |
| assertTrue(z.getClass().equals(clazz)); |
| assertEquals(new Integer(i), expr.evaluate(context)); |
| cctor.clear(); |
| jexl.setClassLoader(null); |
| |
| // on pass 0, attempt to force GC to run and collect generated classes |
| if (pass == 0) { |
| // add a weak reference on the class |
| stuff.add(new ClassReference(clazz, queue)); |
| // add a soft reference on an instance |
| stuff.add(new InstanceReference(clazz.newInstance(), queue)); |
| |
| // attempt to force GC: |
| // while we still have a MB free, create & store big objects |
| for (int b = 0; b < 64 && Runtime.getRuntime().freeMemory() > MEGA; ++b) { |
| BigObject big = new BigObject(b); |
| stuff.add(new InstanceReference(big, queue)); |
| } |
| // hint it... |
| System.gc(); |
| // let's see if some weak refs got collected |
| boolean qr = false; |
| while (queue.poll() != null) { |
| Reference<?> ref = queue.remove(1); |
| if (ref instanceof ClassReference) { |
| gced = i; |
| qr = true; |
| } |
| } |
| if (qr) { |
| //logger.warn("may have GCed class around " + i); |
| pass = 1; |
| i = 0; |
| } |
| } |
| } |
| |
| if (gced < 0) { |
| logger.warn("unable to force GC"); |
| //assertTrue(gced > 0); |
| } |
| } |
| |
| } |