001package com.pi4j.io.i2c.impl;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  I2CProviderImpl.java
009 *
010 * This file is part of the Pi4J project. More information about
011 * this project can be found here:  http://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2016 Pi4J
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU Lesser General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 *
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Lesser Public License for more details.
025 *
026 * You should have received a copy of the GNU General Lesser Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
029 * #L%
030 */
031
032import java.io.IOException;
033import java.util.HashMap;
034import java.util.Map;
035import java.util.concurrent.Callable;
036import java.util.concurrent.TimeUnit;
037import java.util.concurrent.locks.Lock;
038import java.util.concurrent.locks.ReentrantLock;
039
040import com.pi4j.io.i2c.I2CBus;
041import com.pi4j.io.i2c.I2CFactory.UnsupportedBusNumberException;
042import com.pi4j.io.i2c.I2CFactoryProvider;
043
044public abstract class I2CProviderImpl implements I2CFactoryProvider {
045
046    /** Singletons */
047    private static final Map<Integer, I2CBus> busSingletons = new HashMap<>();
048
049    /** to lock the creation/destruction of the bus singletons */
050    private static final Lock singletonPerBusLock = new ReentrantLock(true);
051
052    /**
053     * Factory method that returns bus implementation.
054     *
055     * @param newInstanceCandidate if no bus has been created yet, this instance is used
056     * @return appropriate bus implementation
057     * @throws IOException thrown in case there is a problem opening bus file or bus number is not 0 or 1.
058     */
059    protected static I2CBus getBus(int busNumber, Callable<I2CBusImpl> constructor, long lockAquireTimeout, TimeUnit lockAquireTimeoutUnit) throws UnsupportedBusNumberException, IOException {
060        InterruptedException lockException = null;
061        try {
062            if (singletonPerBusLock.tryLock(lockAquireTimeout, lockAquireTimeoutUnit)) {
063                I2CBus bus;
064                try {
065                    bus = busSingletons.get(busNumber);
066
067                    if (bus == null) {
068                        I2CBusImpl newInstanceCandidate = constructor.call();
069                        newInstanceCandidate.open();
070                        bus = newInstanceCandidate;
071                        busSingletons.put(newInstanceCandidate.busNumber, bus);
072                    }
073                } catch (Exception e) {
074                    throw new RuntimeException("Could not instantiate I2CBus", e);
075                } finally {
076                    singletonPerBusLock.unlock();
077                }
078                return bus;
079            }
080        } catch (InterruptedException e) {
081            lockException = e;
082        }
083        throw new RuntimeException("Could not abtain lock to build new bus!", lockException);
084    }
085
086    protected static void closeBus(int busNumber, long lockAquireTimeout, TimeUnit lockAquireTimeoutUnit, Callable<Void> closeAction) {
087        InterruptedException lockException = null;
088        try {
089            if (singletonPerBusLock.tryLock(lockAquireTimeout, lockAquireTimeoutUnit)) {
090                try {
091                    closeAction.call();
092                    return;
093                } catch (Exception e) {
094                    throw new RuntimeException("Cannot close bus", e);
095                } finally {
096                    //  after closing the fd, we must "forget" the singleton bus instance, otherwise further request to this bus will always fail
097                    try {
098                        busSingletons.remove(busNumber);
099                    } finally {
100                        singletonPerBusLock.unlock();
101                    }
102                }
103            }
104        } catch (InterruptedException e) {
105            lockException = e;
106        }
107
108        throw new RuntimeException("Could not abtain lock to close the bus!", lockException);
109    }
110
111    public I2CBus getBus(final int busNumber, final long lockAquireTimeout, final TimeUnit lockAquireTimeoutUnit) throws UnsupportedBusNumberException, IOException {
112        return getBus(busNumber, new Callable<I2CBusImpl>() {
113            @Override
114            public I2CBusImpl call() throws UnsupportedBusNumberException {
115                return new I2CBusImpl(busNumber, getFilenameForBusnumber(busNumber), lockAquireTimeout, lockAquireTimeoutUnit);
116            }
117        }, lockAquireTimeout, lockAquireTimeoutUnit);
118    }
119
120    protected abstract String getFilenameForBusnumber(int busNumber) throws UnsupportedBusNumberException;
121}