You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/18 17:03:24 UTC

[15/64] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply org.apache package prefix to utils-common

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/io/FileUtil.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/io/FileUtil.java b/utils/common/src/main/java/org/apache/brooklyn/util/io/FileUtil.java
new file mode 100644
index 0000000..cdc65a5
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/io/FileUtil.java
@@ -0,0 +1,202 @@
+/*
+ * 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.brooklyn.util.io;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.io.FileUtil;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.stream.StreamGobbler;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+
+public class FileUtil {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FileUtil.class);
+
+    private static boolean loggedSetFilePermissionsWarning = false;
+    
+    // When we move to java 7, we can use Files.setPosixFilePermissions
+    public static void setFilePermissionsTo700(File file) throws IOException {
+        file.createNewFile();
+        boolean setRead = file.setReadable(false, false) & file.setReadable(true, true);
+        boolean setWrite = file.setWritable(false, false) & file.setWritable(true, true);
+        boolean setExec = file.setExecutable(false, false) & file.setExecutable(true, true);
+        
+        if (setRead && setWrite && setExec) {
+            if (LOG.isTraceEnabled()) LOG.trace("Set permissions to 700 for file {}", file.getAbsolutePath());
+        } else {
+            if (loggedSetFilePermissionsWarning) {
+                if (LOG.isTraceEnabled()) LOG.trace("Failed to set permissions to 700 for file {}: setRead={}, setWrite={}, setExecutable={}",
+                        new Object[] {file.getAbsolutePath(), setRead, setWrite, setExec});
+            } else {
+                if (Os.isMicrosoftWindows()) {
+                    if (LOG.isDebugEnabled()) LOG.debug("Failed to set permissions to 700 for file {}; expected behaviour on Windows; setRead={}, setWrite={}, setExecutable={}; subsequent failures (on any file) will be logged at trace",
+                            new Object[] {file.getAbsolutePath(), setRead, setWrite, setExec});
+                } else {
+                    LOG.warn("Failed to set permissions to 700 for file {}: setRead={}, setWrite={}, setExecutable={}; subsequent failures (on any file) will be logged at trace",
+                            new Object[] {file.getAbsolutePath(), setRead, setWrite, setExec});
+                }
+                loggedSetFilePermissionsWarning = true;
+            }
+        }
+    }
+    
+    // When we move to java 7, we can use Files.setPosixFilePermissions
+    public static void setFilePermissionsTo600(File file) throws IOException {
+        file.createNewFile();
+        file.setExecutable(false, false);
+        file.setReadable(false, false);
+        file.setWritable(false, false);
+        file.setReadable(true, true);
+        file.setWritable(true, true);
+        
+        boolean setRead = file.setReadable(false, false) & file.setReadable(true, true);
+        boolean setWrite = file.setWritable(false, false) & file.setWritable(true, true);
+        boolean setExec = file.setExecutable(false, false);
+        
+        if (setRead && setWrite && setExec) {
+            if (LOG.isTraceEnabled()) LOG.trace("Set permissions to 600 for file {}", file.getAbsolutePath());
+        } else {
+            if (loggedSetFilePermissionsWarning) {
+                if (LOG.isTraceEnabled()) LOG.trace("Failed to set permissions to 600 for file {}: setRead={}, setWrite={}, setExecutable={}",
+                        new Object[] {file.getAbsolutePath(), setRead, setWrite, setExec});
+            } else {
+                if (Os.isMicrosoftWindows()) {
+                    if (LOG.isDebugEnabled()) LOG.debug("Failed to set permissions to 600 for file {}; expected behaviour on Windows; setRead={}, setWrite={}, setExecutable={}; subsequent failures (on any file) will be logged at trace",
+                            new Object[] {file.getAbsolutePath(), setRead, setWrite, setExec});
+                } else {
+                    LOG.warn("Failed to set permissions to 600 for file {}: setRead={}, setWrite={}, setExecutable={}; subsequent failures (on any file) will be logged at trace",
+                            new Object[] {file.getAbsolutePath(), setRead, setWrite, setExec});
+                }
+                loggedSetFilePermissionsWarning = true;
+            }
+        }
+    }
+    
+    public static void moveDir(File srcDir, File destDir) throws IOException, InterruptedException {
+        if (!Os.isMicrosoftWindows()) {
+            String cmd = "mv '"+srcDir.getAbsolutePath()+"' '"+destDir.getAbsolutePath()+"'";
+            Process proc = Runtime.getRuntime().exec(cmd);
+            proc.waitFor();
+            if (proc.exitValue() == 0) return;
+        }
+        
+        FileUtils.moveDirectory(srcDir, destDir);
+    }
+    
+    public static void copyDir(File srcDir, File destDir) throws IOException, InterruptedException {
+        if (!Os.isMicrosoftWindows()) {
+            String cmd = "cp -R '"+srcDir.getAbsolutePath()+"' '"+destDir.getAbsolutePath()+"'";
+            Process proc = Runtime.getRuntime().exec(cmd);
+            proc.waitFor();
+            if (proc.exitValue() == 0) return;
+        }
+        
+        FileUtils.copyDirectory(srcDir, destDir);
+    }
+    
+    /**
+     * This utility will be deleted when we move to Java 7
+     * 
+     * @return The file permission (in a form like "-rwxr--r--"), or null if the permissions could not be determined.
+     */
+    @Beta
+    public static Maybe<String> getFilePermissions(File file) {
+        if (!file.exists()) return Maybe.absent("File "+file+" does not exist");
+        
+        if (Os.isMicrosoftWindows()) {
+            return Maybe.absent("Cannot determine permissions on windows");
+        } else {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            ByteArrayOutputStream err = new ByteArrayOutputStream();
+            int exitcode = exec(ImmutableList.of("ls", "-ld", file.getAbsolutePath()), out, err);
+            if (exitcode != 0) {
+                if (LOG.isDebugEnabled()) LOG.debug("Could not determine permissions of file "+file+"; exit code "+exitcode+"; stderr "+new String(err.toByteArray()));
+                return Maybe.absent("Could not determine permission of file "+file+"; exit code "+exitcode);
+            }
+            String stdout = new String(out.toByteArray());
+            return (stdout.trim().isEmpty() ? Maybe.<String>absent("empty output") : Maybe.of(stdout.split("\\s")[0]));
+        }
+    }
+
+    // guava's Files.copy(InputStreamSupplier, File) is deprecated, and will be deleted in guava 18.0
+    @Beta
+    public static void copyTo(InputStream in, File dest) {
+        FileOutputStream out = null;
+        try {
+            out = new FileOutputStream(dest);
+            Streams.copy(in, out);
+        } catch (FileNotFoundException e) {
+            throw Exceptions.propagate(e);
+        } finally {
+            Streams.closeQuietly(out);
+        }
+    }
+    
+    private static int exec(List<String> cmds, OutputStream out, OutputStream err) {
+        StreamGobbler errgobbler = null;
+        StreamGobbler outgobbler = null;
+        
+        ProcessBuilder pb = new ProcessBuilder(cmds);
+        
+        try {
+            Process p = pb.start();
+            
+            if (out != null) {
+                InputStream outstream = p.getInputStream();
+                outgobbler = new StreamGobbler(outstream, out, (Logger) null);
+                outgobbler.start();
+            }
+            if (err != null) {
+                InputStream errstream = p.getErrorStream();
+                errgobbler = new StreamGobbler(errstream, err, (Logger) null);
+                errgobbler.start();
+            }
+            
+            int result = p.waitFor();
+            
+            if (outgobbler != null) outgobbler.blockUntilFinished();
+            if (errgobbler != null) errgobbler.blockUntilFinished();
+            
+            return result;
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        } finally {
+            Streams.closeQuietly(outgobbler);
+            Streams.closeQuietly(errgobbler);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AggregateClassLoader.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AggregateClassLoader.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AggregateClassLoader.java
new file mode 100644
index 0000000..cea9028
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AggregateClassLoader.java
@@ -0,0 +1,173 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.google.common.collect.Sets;
+
+/** looks for classes and resources in the classloaders added here
+ * <p>
+ * similar to XStream's CompositeClassLoader, but also supporting resources,
+ * exposing more info, a few conveniences, and a nice toString */
+public class AggregateClassLoader extends ClassLoader {
+
+    // thread safe -- and all access in this class is also synchronized, 
+    // so that reset is guaranteed not to interfere with an add(0, cl) 
+    private final CopyOnWriteArrayList<ClassLoader> classLoaders = new CopyOnWriteArrayList<ClassLoader>();
+
+    private AggregateClassLoader() {
+        //Don't pass load requests to the app classloader,
+        //always relay to the classLoaders list.
+        super(null);
+    }
+    
+    /** creates default instance, with classloaders of Object and AggregateClassLoader */
+    public static AggregateClassLoader newInstanceWithDefaultLoaders() {
+        AggregateClassLoader cl = new AggregateClassLoader();
+        cl.addFirst(AggregateClassLoader.class.getClassLoader()); // whichever classloader loaded this jar.
+        cl.addFirst(Object.class.getClassLoader()); // bootstrap loader.
+        return cl;
+    }
+    /** creates default instance, with no classloaders (assuming this instance will itself be nested,
+     * or defaults will be added by caller) */
+    public static AggregateClassLoader newInstanceWithNoLoaders() {
+        return new AggregateClassLoader();
+    }
+
+    /** Add a loader to the first position in the search path. */
+    public void addFirst(ClassLoader classLoader) {
+        if (classLoader != null) {
+            synchronized (classLoaders) {
+                classLoaders.add(0, classLoader);
+            }
+        }
+    }
+    /** Add a loader to the last position in the search path. */
+    public void addLast(ClassLoader classLoader) {
+        if (classLoader != null) {
+            synchronized (classLoaders) {
+                classLoaders.add(classLoader);
+            }
+        }
+    }
+    /** Add a loader to the specific position in the search path. 
+     * (It is callers responsibility to ensure that position is valid.) */
+    public void add(int index, ClassLoader classLoader) {
+        if (classLoader != null) {
+            synchronized (classLoaders) {
+                classLoaders.add(index, classLoader);
+            }
+        }
+    }
+    
+    /** Resets the classloader shown here to be the given set */
+    public void reset(Collection<? extends ClassLoader> newClassLoaders) {
+        synchronized (classLoaders) {
+            // synchronize:
+            // * to prevent concurrent invocations
+            // * so add(0, cl) doesn't interfere
+            // * and for good measure we add before removing so that iterator always contains everything
+            //   although since iterator access is synchronized that shouldn't be necessary
+            int count = classLoaders.size();
+            classLoaders.addAll(newClassLoaders);
+            for (int i=0; i<count; i++) {
+                classLoaders.remove(0);
+            }
+        }
+    }
+
+    /** True if nothing is in the list here */
+    public boolean isEmpty() {
+        return classLoaders.isEmpty();
+    }
+    
+    /** Returns the _live_ (and modifiable) list of classloaders; dangerous and discouraged. 
+     * @deprecated since 0.7.0 */
+    @Deprecated
+    public List<ClassLoader> getList() {
+        return classLoaders;
+    }
+
+    public Iterator<ClassLoader> iterator() {
+        synchronized (classLoaders) {
+            // CopyOnWriteList iterator is immutable view of snapshot
+            return classLoaders.iterator();
+        }
+    }
+    
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        for (ClassLoader classLoader: classLoaders) {
+            try {
+                return classLoader.loadClass(name);
+            } catch (ClassNotFoundException notFound) {
+                /* ignore (nice if there were a better way than throwing... */
+            }
+        }
+        // last resort. see comment in XStream CompositeClassLoader
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        if (contextClassLoader != null)
+            return contextClassLoader.loadClass(name);
+        throw new ClassNotFoundException(name);
+    }
+
+    @Override
+    public String toString() {
+        return "AggregateClassLoader"+classLoaders;
+    }
+
+    @Override
+    public URL getResource(String name) {
+        URL result = null;
+        Iterator<ClassLoader> cli = iterator();
+        while (cli.hasNext()) {
+            ClassLoader classLoader=cli.next();
+            result = classLoader.getResource(name);
+            if (result!=null) return result;
+        }
+        // last resort. see comment in XStream CompositeClassLoader
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        if (contextClassLoader != null) 
+            return contextClassLoader.getResource(name);
+        return null;
+    }
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+        Set<URL> resources = Sets.newLinkedHashSet();
+        Iterator<ClassLoader> cli = iterator();
+        while (cli.hasNext()) {
+            ClassLoader classLoader=cli.next();
+            resources.addAll(Collections.list(classLoader.getResources(name)));
+        }
+        return Collections.enumeration(resources);
+    }
+
+    // TODO lesser used items, such as getPackage, findLibrary
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AtomicReferences.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AtomicReferences.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AtomicReferences.java
new file mode 100644
index 0000000..8042afd
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/AtomicReferences.java
@@ -0,0 +1,48 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+public class AtomicReferences {
+
+    /** sets the atomic reference to the given value, and returns whether there is any change */
+    public static boolean setIfDifferent(AtomicBoolean ref, boolean value) {
+        return ref.getAndSet(value) != value;
+    }
+
+    /** sets the atomic reference to the given value, and returns whether there is any change */
+    public static <T> boolean setIfDifferent(AtomicReference<T> ref, T value) {
+        return !Objects.equal(ref.getAndSet(value), value);
+    }
+    
+    /** returns the given atomic as a Supplier */
+    public static <T> Supplier<T> supplier(final AtomicReference<T> ref) {
+        Preconditions.checkNotNull(ref);
+        return new Supplier<T>() {
+            @Override public T get() { return ref.get(); }
+            @Override public String toString() { return "AtomicRefSupplier"; }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java
new file mode 100644
index 0000000..79a1830
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java
@@ -0,0 +1,102 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import java.lang.reflect.Array;
+
+import org.apache.brooklyn.util.guava.Maybe;
+
+import com.google.common.collect.ImmutableBiMap;
+
+public class Boxing {
+
+    public static boolean unboxSafely(Boolean ref, boolean valueIfNull) {
+        if (ref==null) return valueIfNull;
+        return ref.booleanValue();
+    }
+    
+    public static final ImmutableBiMap<Class<?>, Class<?>> PRIMITIVE_TO_BOXED =
+        ImmutableBiMap.<Class<?>, Class<?>>builder()
+            .put(Integer.TYPE, Integer.class)
+            .put(Long.TYPE, Long.class)
+            .put(Double.TYPE, Double.class)
+            .put(Float.TYPE, Float.class)
+            .put(Boolean.TYPE, Boolean.class)
+            .put(Character.TYPE, Character.class)
+            .put(Byte.TYPE, Byte.class)
+            .put(Short.TYPE, Short.class)
+            .put(Void.TYPE, Void.class)
+            .build();
+    
+    /** Returns the unboxed type for the given primitive type name, if available;
+     * e.g. {@link Integer#TYPE} for <code>int</code> (distinct from <code>Integer.class</code>),
+     * or null if not a primitive.
+     * */
+    public static Maybe<Class<?>> getPrimitiveType(String typeName) {
+        if (typeName!=null) {
+            for (Class<?> t: PRIMITIVE_TO_BOXED.keySet()) {
+                if (typeName.equals(t.getName())) return Maybe.<Class<?>>of(t);
+            }
+        }
+        return Maybe.absent("Not a primitive: "+typeName);
+    }
+    
+    public static Class<?> boxedType(Class<?> type) {
+        if (PRIMITIVE_TO_BOXED.containsKey(type))
+            return PRIMITIVE_TO_BOXED.get(type);
+        return type;
+    }
+
+    /** sets the given element in an array to the indicated value;
+     * if the type is a primitive type, the appropriate primitive method is used
+     * <p>
+     * this is needed because arrays do not deal with autoboxing */
+    public static void setInArray(Object target, int index, Object value, Class<?> type) {
+        if (PRIMITIVE_TO_BOXED.containsKey(type)) {
+            if (type.equals(Integer.TYPE))
+                Array.setInt(target, index, (Integer)value);
+            else if (type.equals(Long.TYPE))
+                Array.setLong(target, index, (Long)value);
+            else if (type.equals(Double.TYPE))
+                Array.setDouble(target, index, (Double)value);
+            else if (type.equals(Float.TYPE))
+                Array.setFloat(target, index, (Float)value);
+            else if (type.equals(Boolean.TYPE))
+                Array.setBoolean(target, index, (Boolean)value);
+            else if (type.equals(Character.TYPE))
+                Array.setChar(target, index, (Character)value);
+            else if (type.equals(Byte.TYPE))
+                Array.setByte(target, index, (Byte)value);
+            else if (type.equals(Short.TYPE))
+                Array.setShort(target, index, (Short)value);
+            
+            else if (type.equals(Void.TYPE))
+                Array.set(target, index, (Void)value);
+            
+            else 
+                // should not happen!
+                throw new IllegalStateException("Unsupported primitive: "+type);
+            
+            return;
+        }
+        
+        Array.set(target, index, value);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Enums.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Enums.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Enums.java
new file mode 100644
index 0000000..d3bfd83
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Enums.java
@@ -0,0 +1,170 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import java.util.Arrays;
+import java.util.Set;
+
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+
+public class Enums {
+    
+    /** returns a function which given an enum, returns its <code>name()</code> function 
+     * @deprecated since 0.7.0 use {@link #nameFunction()} to avoid inner class */
+    @Deprecated
+    public static Function<Enum<?>,String> enumValueNameFunction() {
+        return new Function<Enum<?>,String>() {
+            @Override
+            public String apply(Enum<?> input) {
+                return input.name();
+            }
+        };
+    }
+
+    private static final class EnumToNameFunction implements Function<Enum<?>, String> {
+        @Override
+        public String apply(Enum<?> input) {
+            return input.name();
+        }
+    }
+
+    /** returns a function which given an enum, returns its <code>name()</code> function */
+    public static Function<Enum<?>,String> nameFunction() {
+        return new EnumToNameFunction();
+    }
+
+    private static final class EnumFromStringFunction<T extends Enum<?>> implements Function<String,T> {
+        private final Class<T> type;
+        public EnumFromStringFunction(Class<T> type) { this.type = type; }
+        @Override
+        public T apply(String input) {
+            return valueOfIgnoreCase(type, input).orNull();
+        }
+    }
+
+    /** returns a function which given a string, produces an enum of the given type or null */
+    public static <T extends Enum<?>> Function<String,T> fromStringFunction(Class<T> type) {
+        return new EnumFromStringFunction<T>(type);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T extends Enum<?>> T[] values(Class<T> type) {
+        try {
+            return (T[]) type.getMethod("values").invoke(null);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
+    /** as {@link #checkAllEnumeratedIgnoreCase(String, Enum[], String...)} using the same default strategy
+     * that {@link #valueOfIgnoreCase(Class, String)} applies */
+    public static void checkAllEnumeratedIgnoreCase(Class<? extends Enum<?>> type, String ...explicitValues) {
+        checkAllEnumeratedIgnoreCase(JavaClassNames.simpleClassName(type), values(type), explicitValues);
+    }
+    /** checks that all accepted enum values are represented by the given set of explicit values */
+    public static void checkAllEnumeratedIgnoreCase(String contextMessage, Enum<?>[] enumValues, String ...explicitValues) {
+        MutableSet<String> explicitValuesSet = MutableSet.copyOf(Iterables.transform(Arrays.asList(explicitValues), StringFunctions.toLowerCase()));
+        
+        Set<Enum<?>> missingEnums = MutableSet.of();
+        for (Enum<?> e: enumValues) {
+            if (explicitValuesSet.remove(e.name().toLowerCase())) continue;
+            if (explicitValuesSet.remove(e.toString().toLowerCase())) continue;
+            
+            if (explicitValuesSet.remove(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, e.name()).toLowerCase())) continue;
+            if (explicitValuesSet.remove(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, e.toString()).toLowerCase())) continue;
+            
+            if (explicitValuesSet.remove(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, e.toString()).toLowerCase())) continue;
+            if (explicitValuesSet.remove(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, e.name()).toLowerCase())) continue;
+            
+            missingEnums.add(e);
+        }
+        
+        if (!missingEnums.isEmpty() || !explicitValuesSet.isEmpty()) {
+            throw new IllegalStateException("Not all options for "+contextMessage+" are enumerated; "
+                + "leftover enums = "+missingEnums+"; "
+                + "leftover values = "+explicitValuesSet);
+        }
+    }
+    
+    /** as {@link #valueOfIgnoreCase(String, Enum[], String)} for all values of the given enum and using the enum type as the message */
+    public static <T extends Enum<?>> Maybe<T> valueOfIgnoreCase(Class<T> type, String givenValue) {
+        return valueOfIgnoreCase(JavaClassNames.simpleClassName(type), values(type), givenValue);
+    }
+    
+    /** attempts to match the givenValue against the given enum values, first looking for exact matches (against name and toString),
+     * then matching ignoring case, 
+     * then matching with {@link CaseFormat#UPPER_UNDERSCORE} converted to {@link CaseFormat#LOWER_CAMEL},
+     * then matching with {@link CaseFormat#LOWER_CAMEL} converted to {@link CaseFormat#UPPER_UNDERSCORE}
+     * (including case insensitive matches for the final two)
+     **/
+    public static <T extends Enum<?>> Maybe<T> valueOfIgnoreCase(String contextMessage, T[] enumValues, String givenValue) {
+        if (givenValue==null) 
+            return Maybe.absent(new IllegalStateException("Value for "+contextMessage+" must not be null"));
+        if (Strings.isBlank(givenValue)) 
+            return Maybe.absent(new IllegalStateException("Value for "+contextMessage+" must not be blank"));
+        
+        for (T v: enumValues)
+            if (v.name().equals(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (v.toString().equals(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (v.name().equalsIgnoreCase(givenValue)) return 
+                Maybe.of(v);
+        for (T v: enumValues)
+            if (v.toString().equalsIgnoreCase(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, v.name()).equals(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, v.toString()).equals(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, v.name()).equalsIgnoreCase(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, v.toString()).equalsIgnoreCase(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, v.toString()).equalsIgnoreCase(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, v.name()).equals(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, v.toString()).equals(givenValue)) 
+                return Maybe.of(v);
+        for (T v: enumValues)
+            if (CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, v.name()).equalsIgnoreCase(givenValue)) 
+                return Maybe.of(v);
+        
+        return Maybe.absent(new IllegalStateException("Invalid value "+givenValue+" for "+contextMessage));
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Equals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Equals.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Equals.java
new file mode 100644
index 0000000..cf17197
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Equals.java
@@ -0,0 +1,93 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import java.lang.reflect.Field;
+
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+
+
+public class Equals {
+
+    private static final Logger log = LoggerFactory.getLogger(Equals.class);
+    
+    /** Tests whether the objects given are either all null or all equal to the first argument */
+    public static boolean objects(Object o1, Object o2, Object... oo) {
+        if (!Objects.equal(o1, o2)) return false;
+        for (Object o: oo) 
+            if (!Objects.equal(o1, o)) return false;
+        return true;
+    }
+
+    /** Tests whether the two objects given are either all null or all approximately equal 
+     * (tolerance of 0.001 for floating point, but subject to change) */
+    // relatively high tolerance mainly due to enrichers such as Tomcat windowed average, in hot standby;
+    // could make smaller
+    @Beta
+    public static boolean approximately(Object o1, Object o2) {
+        if (o1 instanceof Number) {
+            if (o2 instanceof Number) {
+                return Math.abs( ((Number)o2).doubleValue()-((Number)o1).doubleValue() ) < 0.001;
+            }
+        }
+        return Objects.equal(o1, o2);
+    }
+
+    /** As {@link #approximately(Object, Object)} but testing all the arguments given. */
+    @Beta
+    public static boolean approximately(Object o1, Object o2, Object o3, Object... oo) {
+        if (!approximately(o1, o2)) return false;
+        if (!approximately(o1, o3)) return false;
+        for (Object o: oo) 
+            if (!approximately(o1, o)) return false;
+        return true;        
+    }
+
+    /** Useful for debugging EqualsBuilder.reflectionEquals */
+    public static void dumpReflectiveEquals(Object o1, Object o2) {
+        log.info("Comparing: "+o1+" "+o2);
+        Class<?> clazz = o1.getClass();
+        while (!(clazz.equals(Object.class))) {
+            log.info("  fields in: "+clazz);
+            for (Field f: clazz.getDeclaredFields()) {
+                f.setAccessible(true);
+                try {
+                    log.info( "    "+(Objects.equal(f.get(o1), f.get(o2)) ? "==" : "!=" ) +
+                        " "+ f.getName()+ " "+ f.get(o1) +" "+ f.get(o2) +
+                        " ("+ classOf(f.get(o1)) +" "+ classOf(f.get(o2)+")") );
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                    log.info( "    <error> "+e);
+                }
+            }
+            clazz = clazz.getSuperclass();
+        }
+    }
+
+    private static String classOf(Object o) {
+        if (o==null) return null;
+        return o.getClass().toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/JavaClassNames.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/JavaClassNames.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/JavaClassNames.java
new file mode 100644
index 0000000..fb8e4be
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/JavaClassNames.java
@@ -0,0 +1,162 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.Preconditions;
+import com.google.common.reflect.TypeToken;
+
+public class JavaClassNames {
+
+    private static final StackTraceSimplifier STACK_TRACE_SIMPLIFIER_EXCLUDING_UTIL_JAVALANG = 
+            StackTraceSimplifier.newInstance(StackTraceSimplifier.class.getPackage().getName()+".");
+    
+    /** returns the Class of anything which isn't a class; if input is class it is pass-through */
+    public static Class<?> type(Object x) {
+        if (x==null) return null;
+        if (x instanceof Class) return (Class<?>)x;
+        if (x instanceof TypeToken) return ((TypeToken<?>)x).getRawType();
+        return x.getClass();
+    }
+
+    /** like type, but removes any array modifiers */
+    public static Class<?> componentType(Object x) {
+        Class<?> c = type(x);
+        if (c==null) return null;
+        while (c.isArray()) {
+            c = c.getComponentType();
+        }
+        return c;
+    }
+
+    /**  returns a simplified name of the class, just the simple name if it seems useful, else the full name */
+    public static String simpleClassName(Class<?> t) {
+        int arrayCount = 0;
+        while (t.isArray()) {
+            arrayCount++;
+            t = t.getComponentType();
+        }
+        Class<?> ct = componentType(t);
+        
+        String result = ct.getSimpleName();
+        if (Strings.isBlank(result) || result.length()<=4) {
+            if (ct.isPrimitive()) {
+                // TODO unbox
+            } else {
+                result = ct.getName();
+            }
+        }
+        return result+Strings.repeat("[]", arrayCount);
+    }
+    
+    /** as {@link #simpleClassName(Class)} but taking the type of the object if it is not already a class
+     * or a type-token; callers should usually do the getClass themselves, unless they aren't sure whether
+     * it is already a Class-type object */
+    public static String simpleClassName(Object x) {
+        if (x==null) return null;
+        return simpleClassName(type(x));
+    }
+
+    /** as {@link #simpleClassName(Class)} but taking a string rep'n of the class name,
+     * and doing best effort to simplify it (without instantiating) */
+    public static String simplifyClassName(String className) {
+        if (className==null) return null;
+        int lastDot = className.lastIndexOf('.');
+        if (lastDot < className.length()-5)
+            return className.substring(lastDot+1);
+        return className;
+    }
+
+    /** as {@link #simpleClassName(Object)} but making the result clean for use on filesystems and as java identifiers */
+    public static String cleanSimpleClassName(Object x) {
+        return Strings.makeValidFilename(simpleClassName(x));
+    }
+    
+    /** as {@link #simpleClassName(Object)} but making the result clean for use on filesystems and as java identifiers */
+    public static String cleanSimpleClassName(Class<?> x) {
+        return Strings.makeValidFilename(simpleClassName(x));
+    }
+    
+    public static String packageName(Object x) {
+        return componentType(x).getPackage().getName();
+    }
+
+    /** returns e.g. "/com/acme/" for an object in package com.acme */ 
+    public static String packagePath(Object x) {
+        return Urls.mergePaths("/", componentType(x).getPackage().getName().replace('.', '/'), "/");
+    }
+
+    /** returns path relative to the package of x, unless path is absolute.
+     * useful to mimic Class.getResource(path) behaviour, cf Class.resolveName where the first argument below is the class. */
+    public static String resolveName(Object context, String path) {
+        Preconditions.checkNotNull(path, "path must not be null");
+        if (path.startsWith("/") || Urls.isUrlWithProtocol(path)) return path;
+        Preconditions.checkNotNull(context, "context must not be null when path is relative");
+        return packagePath(context)+path;
+    }
+
+    /** returns a "classpath:" URL given a context object and a file to be found in that directory or a sub-directory
+     * (ignoring the context object if the given path is absolute, i.e. starting with "/" or "protocol:") 
+     * e.g. "classpath://com/acme/foo.txt" given a context object com.acme.SomeClass and "foo.txt" */
+    public static String resolveClasspathUrl(Object context, String path) {
+        if (Urls.isUrlWithProtocol(path)) return path;
+        // additional / comes from resolve name
+        return "classpath:/"+resolveName(context, path);
+    }
+
+    /** returns a cleaned stack trace; caller is usually at the top */
+    public static StackTraceElement[] currentStackTraceCleaned() {
+        return STACK_TRACE_SIMPLIFIER_EXCLUDING_UTIL_JAVALANG.clean(
+                Thread.currentThread().getStackTrace());
+    }
+    
+    /** returns top of cleaned stack trace; usually the caller's location */
+    public static StackTraceElement currentStackElement() {
+        return STACK_TRACE_SIMPLIFIER_EXCLUDING_UTIL_JAVALANG.nthUseful(0,
+                Thread.currentThread().getStackTrace());
+    }
+
+    /** returns element in cleaned stack trace; usually the caller's location is at the top,
+     * and caller of that is up one, etc */
+    public static StackTraceElement callerStackElement(int depth) {
+        return STACK_TRACE_SIMPLIFIER_EXCLUDING_UTIL_JAVALANG.nthUseful(depth,
+                Thread.currentThread().getStackTrace());
+    }
+
+    /** returns nice class name and method for the given element */
+    public static String niceClassAndMethod(StackTraceElement st) {
+        return simplifyClassName(st.getClassName())+"."+st.getMethodName();
+    }
+
+    /** returns nice class name and method for the caller, going up the stack (filtered to remove invocation etc),
+     * with 0 typically being the context where this method is called, 1 being its caller, etc */
+    public static String callerNiceClassAndMethod(int depth) {
+        return niceClassAndMethod(callerStackElement(depth));
+    }
+
+    /** convenience for {@link #callerNiceClassAndMethod(int)} with depth 0
+     * <p>
+     * useful for tests and other debug-facing log messages! */
+    public static String niceClassAndMethod() {
+        return callerNiceClassAndMethod(0);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/LoadedClassLoader.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/LoadedClassLoader.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/LoadedClassLoader.java
new file mode 100644
index 0000000..dba726f
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/LoadedClassLoader.java
@@ -0,0 +1,44 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** a classloader which allows you to register classes and resources which this loader will return when needed,
+ * (essentially a registry rather than a classloader, but useful if you need to make new classes available in
+ * an old context) */
+public class LoadedClassLoader extends ClassLoader {
+
+    Map<String, Class<?>> loadedClasses = new LinkedHashMap<String, Class<?>>();
+    
+    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        Class<?> result = loadedClasses.get(name);
+        if (result==null) throw new ClassNotFoundException(""+name+" not known here");
+        if (resolve) resolveClass(result);
+        return result;
+    }
+
+    public void addClass(Class<?> clazz) {
+        loadedClasses.put(clazz.getName(), clazz);
+    }
+    
+    // TODO could also add resources
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/MemoryUsageTracker.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/MemoryUsageTracker.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/MemoryUsageTracker.java
new file mode 100644
index 0000000..e3897cc
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/MemoryUsageTracker.java
@@ -0,0 +1,72 @@
+/*
+ * 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.brooklyn.util.javalang;
+
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+
+/** 
+ * Tracks the amount of memory consumed by the given objects in use.
+ * <p>
+ * {@link WeakReference}s are used internally, so that shortly after a {@link #track(Object, long)}ed object is GC'd, 
+ * the {@link #getBytesUsed()} value decrements appropriately.
+ */
+public class MemoryUsageTracker {
+
+    /**
+     * Shared instance for use for tracking memory used by {@link SoftReference}.
+     * <p>
+     * Callers should only use this field to {@link #track(Object, long)} objects which have (or will soon have)
+     * given up their strong references, so that only soft or weak references remain.
+     * Provided size estimates are accurate, {@link #getBytesUsed()} will report
+     * the amount of used memory which is reclaimable by collecting soft references.
+     * <p>
+     * This is particularly handy for tracking {@link SoftReference}s, because otherwise you can quickly get to a state
+     * where {@link Runtime#freeMemory()} looks very low.
+     **/
+    public static final MemoryUsageTracker SOFT_REFERENCES = new MemoryUsageTracker();
+    
+    AtomicLong bytesUsed = new AtomicLong(0);
+    
+    Cache<Object, Long> memoryTrackedReferences = CacheBuilder.newBuilder()
+            .weakKeys()
+            .removalListener(new RemovalListener<Object,Long>() {
+                @Override
+                public void onRemoval(RemovalNotification<Object, Long> notification) {
+                    bytesUsed.addAndGet(-notification.getValue());
+                }
+            }).build();
+    
+    public void track(Object instance, long bytesUsedByInstance) {
+        bytesUsed.addAndGet(bytesUsedByInstance);
+        memoryTrackedReferences.put(instance, bytesUsedByInstance);
+    }
+    
+    public long getBytesUsed() {
+        memoryTrackedReferences.cleanUp();
+        return bytesUsed.get();
+    }
+    
+}