/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.core.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.SynchronousBundleListener;
import org.eclipse.riena.core.exception.Failure;
import org.eclipse.riena.core.wire.Wire;
/**
* The utility class {@code Companion} tries to mimic the companion object known from Scala.
* <p>
* With this it is possible to create a single instance for any (a few restrictions of course) given class. It should be noted that this is different from the
* <i>singleton pattern</i> where the class which implements the singleton has to have a static {@code getInstance()} method. The {@code Companion} does not
* need this!
* <p>
* However, classes that can be used with the {@code Companion} must implement a default constructor. The default constructor can be {@code private} or
* {@code protected} if there is no security policy preventing to change the accessibility.
* <p>
* The {@code Companion} also <b>wires</b> its single object. The companion object will be hold as long as the bundle containing the companion class is active.<br>
* This behavior ensures that also the class may be garbage collected and thus the whole bundle could be stopped.
*
* @since 4.0
*/
public final class Companion {
private static final Map<Class<Object>, Object> CLASS_TO_COMPANION = new HashMap<Class<Object>, Object>();
private static final Map<Bundle, List<Class<Object>>> BUNDLE_TO_CLASSES = new HashMap<Bundle, List<Class<Object>>>();
private static final Bundle THIS_BUNDLE = FrameworkUtil.getBundle(Companion.class);
private static final SynchronousBundleListener FREE_ME = new FreeMe();
private static final Object LOCK = new Object();
static {
if (THIS_BUNDLE != null) {
final BundleContext bundleContext = THIS_BUNDLE.getBundleContext();
if (bundleContext != null) {
bundleContext.addBundleListener(FREE_ME);
}
}
}
private Companion() {
// utility
}
/**
* Get the companion object for the given class.
*
* @param companionClass
* the class to get the companion object for
* @return the companion object
*/
public static synchronized <T> T per(final Class<T> companionClass) {
synchronized (LOCK) {
T companion = (T) CLASS_TO_COMPANION.get(companionClass);
if (companion == null) {
companion = createCompanion(companionClass);
Wire.instance(companion).andStart();
register(companionClass, companion);
}
return companion;
}
}
/**
* @since 5.0
*/
public static synchronized void reset() {
synchronized (LOCK) {
CLASS_TO_COMPANION.clear();
}
}
@SuppressWarnings("unchecked")
private static <T> void register(final Class<T> companionClass, final T companion) {
CLASS_TO_COMPANION.put((Class<Object>) companionClass, (Object) companion);
final Bundle bundle = FrameworkUtil.getBundle(companionClass);
if (bundle == null) {
return;
}
List<Class<Object>> classes = BUNDLE_TO_CLASSES.get(bundle);
if (classes == null) {
classes = new ArrayList<Class<Object>>();
BUNDLE_TO_CLASSES.put(bundle, classes);
}
classes.add((Class<Object>) companionClass);
}
private final static class FreeMe implements SynchronousBundleListener {
public void bundleChanged(final BundleEvent event) {
if (event.getType() != BundleEvent.STOPPING) {
return;
}
synchronized (LOCK) {
final List<Class<Object>> remove = BUNDLE_TO_CLASSES.remove(event.getBundle());
for (final Class<Object> clazz : Iter.able(remove)) {
CLASS_TO_COMPANION.remove(clazz);
}
}
if (event.getBundle().equals(THIS_BUNDLE)) {
final BundleContext bundleContext = THIS_BUNDLE.getBundleContext();
if (bundleContext != null) {
bundleContext.removeBundleListener(FREE_ME);
}
}
}
}
private static <T> T createCompanion(final Class<T> companionClass) {
try {
final Constructor<T> constructor = companionClass.getDeclaredConstructor();
final boolean isAccessible = constructor.isAccessible();
try {
if (!isAccessible) {
constructor.setAccessible(true);
}
return constructor.newInstance();
} finally {
if (!isAccessible) {
constructor.setAccessible(false);
}
}
} catch (final SecurityException e) {
throw new CompanionCreationFailure("Could not create an instance for " + companionClass.getName(), e); //$NON-NLS-1$
} catch (final NoSuchMethodException e) {
throw new CompanionCreationFailure("Could not create an instance for " + companionClass.getName(), e); //$NON-NLS-1$
} catch (final IllegalArgumentException e) {
throw new CompanionCreationFailure("Could not create an instance for " + companionClass.getName(), e); //$NON-NLS-1$
} catch (final InstantiationException e) {
throw new CompanionCreationFailure("Could not create an instance for " + companionClass.getName(), e); //$NON-NLS-1$
} catch (final IllegalAccessException e) {
throw new CompanionCreationFailure("Could not create an instance for " + companionClass.getName(), e); //$NON-NLS-1$
} catch (final InvocationTargetException e) {
throw new CompanionCreationFailure("Could not create an instance for " + companionClass.getName(), e); //$NON-NLS-1$
}
}
private final static class CompanionCreationFailure extends Failure {
private static final long serialVersionUID = 1L;
public CompanionCreationFailure(final String msg, final Throwable cause) {
super(msg, cause);
}
}
}