DefaultJLibnotifyLoader.java

/*
 * Copyright 2019 Hector Espert <hectorespertpardo@gmail.com>.
 *
 * Licensed 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 es.blackleg.jlibnotify.core;

import com.sun.jna.DefaultTypeMapper;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.platform.EnumConverter;
import es.blackleg.jlibnotify.exception.JLibnotifyLoadException;
import es.blackleg.jlibnotify.jna.GBoolean;
import java.util.HashMap;
import java.util.Map;
import es.blackleg.jlibnotify.JLibnotifyLoader;
import es.blackleg.jlibnotify.JLibnotify;
import es.blackleg.jlibnotify.jna.NativeLibnotify;

/**
 * Reference implementation of {@link JLibnotifyLoader}, and the entry point of the library.
 *
 * <p>{@link #init()} builds a loader for the default library name, so a complete start looks
 * like:</p>
 *
 * <pre>{@code
 * JLibnotify jLibnotify = DefaultJLibnotifyLoader.init().load();
 * jLibnotify.init("My Application");
 * }</pre>
 *
 * <p>This is the only class of {@link es.blackleg.jlibnotify.core} application code is expected to
 * name; everything else is reached through the interfaces of {@link es.blackleg.jlibnotify}.</p>
 *
 * @author Hector Espert
 */
public class DefaultJLibnotifyLoader implements JLibnotifyLoader {

    /**
     * Name of the shared library loaded when none is given: version 4 of libnotify.
     */
    private static final String DEFAULT_LIBRARY_NAME = "libnotify.so.4";

    private final String libraryName;

    private final ServerCapabilitiesReader serverCapabilitiesReader;

    /**
     * Creates a loader for the default library name, {@code libnotify.so.4}.
     */
    public DefaultJLibnotifyLoader() {
        this(DEFAULT_LIBRARY_NAME);
    }

    /**
     * Creates a loader for a given library name.
     *
     * <p>Useful to load another version of libnotify, or a copy of it installed outside the
     * standard library path.</p>
     *
     * @param libraryName name of the shared library, as understood by
     *                    {@link com.sun.jna.Native#load(String, Class, Map)}
     */
    public DefaultJLibnotifyLoader(String libraryName) {
        this(libraryName, new DefaultServerCapabilitiesReader());
    }

    /**
     * Creates a loader for a given library name and capabilities reader.
     *
     * @param libraryName              name of the shared library, as understood by
     *                                 {@link com.sun.jna.Native#load(String, Class, Map)}
     * @param serverCapabilitiesReader reader the loaded library will use to walk the capabilities
     *                                 returned by the notification server
     */
    public DefaultJLibnotifyLoader(String libraryName, ServerCapabilitiesReader serverCapabilitiesReader) {
        this.libraryName = libraryName;
        this.serverCapabilitiesReader = serverCapabilitiesReader;
    }

    /**
     * Loads the native library and binds it to a {@link DefaultJLibnotify}.
     *
     * <p>Registers a type mapper converting the C {@code gboolean} results into {@link GBoolean}
     * before handing the interface to JNA. Native loading fails with an {@link Error} rather than
     * an exception, so every {@link Throwable} is caught and reported as a checked
     * {@link JLibnotifyLoadException}.</p>
     *
     * @return a binding of the loaded library, not yet initialised
     * @throws JLibnotifyLoadException if the library is missing or cannot be loaded, which is the
     *                                 case on any platform other than Linux
     */
    @Override
    public JLibnotify load() throws JLibnotifyLoadException {
        try {
            EnumConverter enumConverter = new EnumConverter(GBoolean.class);
            DefaultTypeMapper defaultTypeMapper = new DefaultTypeMapper();
            defaultTypeMapper.addTypeConverter(GBoolean.class, enumConverter);
            Map<String, Object> options = new HashMap<>();
            options.put(Library.OPTION_TYPE_MAPPER, defaultTypeMapper);
            NativeLibnotify nativeLibNotify = Native.load(this.libraryName, NativeLibnotify.class, options);
            return new DefaultJLibnotify(nativeLibNotify, this.serverCapabilitiesReader);
        } catch (Throwable throwable) {
            throw new JLibnotifyLoadException(String.format("Unable to load %s library", this.libraryName), throwable);
        }
    }

    /**
     * Creates a loader for the default library name, {@code libnotify.so.4}.
     *
     * <p>Starting point of the library, meant to be chained with {@link #load()}.</p>
     *
     * @return a loader ready to load the default libnotify library
     */
    public static JLibnotifyLoader init() {
        return new DefaultJLibnotifyLoader();
    }

}