package com.davfx.ninio.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Charsets;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;

public final class ConfigUtils {

	private static final Logger LOGGER = LoggerFactory.getLogger(ConfigUtils.class);

	private ConfigUtils() {
	}

	public static double getDuration(Config c, String key) {
		return c.getDuration(key, TimeUnit.NANOSECONDS) / 1_000_000_000d;
	}

	public static char getChar(Config c, String key) {
		String s = c.getString(key);
		if (s.length() != 1) {
			throw new ConfigException.BadValue(key, "Invalid value: " + s + ". Char value must be a string with only one character.");
		}
		return s.charAt(0);
	}

	//

	private static InputStream getResource(Dependencies dependencies, String resource) {
		LOGGER.trace("Getting resource: {} with {}", resource + ".conf", dependencies.getClass().getClassLoader());
		InputStream i = dependencies.getClass().getClassLoader().getResourceAsStream(resource + ".conf");
		if (i == null) {
			LOGGER.trace("Getting resource: {} with {}", resource + ".conf --> null", dependencies.getClass().getClassLoader());
			for (Dependencies d : dependencies.dependencies()) {
				i = getResource(d, resource);
				if (i != null) {
					return i;
				}
			}
			return null;
		} else {
			return i;
		}
	}

	private static String loadConfig(Dependencies dependencies, String resource, boolean warn) throws IOException {
		File rootDir = new File(".");
		LOGGER.trace("Absolute path: {}", rootDir.getAbsolutePath());
		InputStream i;
		File f = new File(rootDir, resource + ".conf");
		if (f.exists()) {
			i = new FileInputStream(f);
		} else {
			i = getResource(dependencies, resource);
			if (i == null) {
				if (warn) {
					LOGGER.warn("Config file not found: {}", resource);
					throw new RuntimeException("Config file not found: " + resource);
				}
				return "";
			}
		}
		try (BufferedReader r = new BufferedReader(new InputStreamReader(i, Charsets.UTF_8))) {
			StringBuilder b = new StringBuilder();
			while (true) {
				String line = r.readLine();
				if (line == null) {
					return b.toString();
				}
				String l = line.trim();
				if (l.startsWith("include ")) {
					b.append(loadConfig(dependencies, l.substring("include ".length()).trim(), true));
				} else {
					b.append(line);
				}
				b.append("\n");
			}
		}
	}

	public static final class Override {
		private final List<String> override = new LinkedList<>();
		public Override() {
		}

		public Override add(String config) {
			override.add(config);
			return this;
		}
		public Override add(String key, String value) {
			override.add(key + " = \"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"");
			return this;
		}
	}

	// Application conf (configure.conf is used over resource)
	public static synchronized Config load(Dependencies dependencies, String resource) {
		return load(dependencies, resource, new Override());
	}

	private static void gatherDependencies(Dependencies dependencies, List<Dependencies> l) {
		for (Dependencies d : dependencies.dependencies()) {
			gatherDependencies(d, l);
		}
		if (!l.contains(dependencies)) {
			l.add(dependencies);
		}
	}

	public static synchronized Config load(Dependencies dependencies, String resource, Override override) {
		StringBuilder c = new StringBuilder();

		List<Dependencies> l = new LinkedList<>();
		gatherDependencies(dependencies, l);
		for (Dependencies d : l) {
			String packageName = d.getClass().getPackage().getName();
			LOGGER.trace("Dependency conf: {}", packageName);
			if (!packageName.endsWith(".dependencies")) {
				throw new RuntimeException("Must ends with '.dependencies': " + packageName);
			}
			packageName = packageName.substring(0, packageName.length() - ".dependencies".length());
			String r;
			try {
				r = loadConfig(d, packageName, true);
			} catch (Exception e) {
				throw new RuntimeException("Could not load package config", e);
			}
			c.append(r);
		}

		if (resource != null) {
			String r;
			try {
				r = loadConfig(dependencies, resource, true);
			} catch (Exception e) {
				throw new RuntimeException("Could not load package config", e);
			}
			c.append(r);
		}

		{
			String a;
			try {
				a = loadConfig(dependencies, "configure-" + resource, false);
			} catch (Exception e) {
				throw new RuntimeException("Could not load application config", e);
			}
			c.append('\n');
			c.append(a);
		}
		{
			String a;
			try {
				a = loadConfig(dependencies, "configure", false);
			} catch (Exception e) {
				throw new RuntimeException("Could not load application config", e);
			}
			c.append('\n');
			c.append(a);
		}

		for (String o : override.override) {
			c.append('\n');
			c.append(o);
		}

		String conf = c.toString();

		LOGGER.trace("Config: \n{}\n", conf);

		return ConfigFactory.parseString(conf).resolve();
	}

	// Static conf, overriding is done with configure.conf
	public static synchronized Config load(Dependencies dependencies) {
		return load(dependencies, null, new Override());
	}
}


package com.davfx.ninio.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.davfx.ninio.core.dependencies.Dependencies;
import com.davfx.ninio.util.ConfigUtils;
import com.typesafe.config.Config;

public final class Trust {

	private static final Logger LOGGER = LoggerFactory.getLogger(Trust.class);

	private static final Config CONFIG = ConfigUtils.load(new Dependencies()).getConfig(Trust.class.getPackage().getName());
	private static final boolean INSECURE = CONFIG.getBoolean("insecure");
	private static final String TLS_VERSION = CONFIG.getString("tls");

	private final KeyStore ksKeys;
	private final SSLContext sslContext;

	public Trust(File keysTrust, String keysTrustPassPhrase) {
		this(keysTrust, keysTrustPassPhrase, keysTrust, keysTrustPassPhrase);
	}

	public Trust(File keys, String keysPassPhrase, File trust, String trustPassPhrase) {
		try {
			ksKeys = KeyStore.getInstance("JKS");
			try (InputStream in = new FileInputStream(keys)) {
				ksKeys.load(in, keysPassPhrase.toCharArray());
			}
			Enumeration<String> e = ksKeys.aliases();
			while (e.hasMoreElements()) {
				String alias = e.nextElement();
				LOGGER.trace("Alias in key store: {}", alias);
			}
			KeyStore ksTrust = KeyStore.getInstance("JKS");
			try (InputStream in = new FileInputStream(trust)) {
				ksTrust.load(in, trustPassPhrase.toCharArray());
			}

			KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
			kmf.init(ksKeys, keysPassPhrase.toCharArray());

			TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
			tmf.init(ksTrust);

			sslContext = SSLContext.getInstance(TLS_VERSION);
			sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public Trust(String keysResourceName, String keysTrustPassPhrase) {
		this(keysResourceName, keysTrustPassPhrase, keysResourceName, keysTrustPassPhrase);
	}

	public Trust(String keysResourceName, String keysPassPhrase, String trustResourceName, String trustPassPhrase) {
		try {
			ksKeys = KeyStore.getInstance("JKS");
			try (InputStream in = Trust.class.getResourceAsStream(keysResourceName)) {
				ksKeys.load(in, keysPassPhrase.toCharArray());
			}
			Enumeration<String> e = ksKeys.aliases();
			while (e.hasMoreElements()) {
				String alias = e.nextElement();
				LOGGER.trace("Alias in key store: {}", alias);
			}
			KeyStore ksTrust = KeyStore.getInstance("JKS");
			try (InputStream in = Trust.class.getResourceAsStream(trustResourceName)) {
				ksTrust.load(in, trustPassPhrase.toCharArray());
			}

			KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
			kmf.init(ksKeys, keysPassPhrase.toCharArray());

			TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
			tmf.init(ksTrust);

			sslContext = SSLContext.getInstance(TLS_VERSION);
			sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public Trust() {
		ksKeys = null;
		try {
			/*%%
			KeyStore ksTrust = KeyStore.getInstance("JKS");

			TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
			tmf.init(ksTrust);

			TrustManager defaultTrustManager = new SavingTrustManager((X509TrustManager) tmf.getTrustManagers()[0]);

			TrustManager[] t = new TrustManager[] { defaultTrustManager }
			*/

			// WARNING! Not secure to a man-in-the-middle attack
			TrustManager[] t = INSECURE ? new TrustManager[] {
				new X509TrustManager() {
					@Override
					public java.security.cert.X509Certificate[] getAcceptedIssuers() {
						return null;
					}
					@Override
					public void checkClientTrusted(X509Certificate[] certs, String authType) {
					}
					@Override
					public void checkServerTrusted(X509Certificate[] certs, String authType) {
					}
				}
			} : null;

			sslContext = SSLContext.getInstance(TLS_VERSION);
			sslContext.init(null, t, null);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/*%%
	private static class SavingTrustManager implements X509TrustManager {

		private final X509TrustManager tm;
		private X509Certificate[] chain;

		SavingTrustManager(X509TrustManager tm) {
			this.tm = tm;
		}

		@Override
		public X509Certificate[] getAcceptedIssuers() {
			throw new UnsupportedOperationException();
		}

		@Override
		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			this.chain = chain;
			tm.checkServerTrusted(chain, authType);
		}
	}
	*/

	public PrivateKey getPrivateKey(String alias, String password) {
		try {
			return (PrivateKey) ksKeys.getKey(alias, password.toCharArray());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	public PublicKey getPublicKey(String alias) {
		try {
			return ksKeys.getCertificate(alias).getPublicKey();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public SSLEngine createEngine(boolean clientMode) {
		SSLEngine engine = sslContext.createSSLEngine();
		engine.setUseClientMode(clientMode);
		return engine;
	}
}


package com.davfx.ninio.string;

import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public final class StringHandler<T> {
	// Beware, with the current algorithm, this function cannot find '\'
	private static final int find(boolean skipBlocks, String s, char toFind, int from) {
		int open = 0;
		boolean escaping = false;
		for (int i = from; i < s.length(); i++) {
			if (escaping) {
				escaping = false;
				continue;
			}
			char c = s.charAt(i);
			if (c == '\\') {
				escaping = true;
				continue;
			}
			if ((open == 0) && (c == toFind)) {
				return i;
			} else if (skipBlocks && (c == '{')) {
				open++;
			} else if (skipBlocks && (c == '}')) {
				open--;
			}
		}
		return -1;
	}

	private final Map<String, StringInputFactory<T>> factories = new HashMap<>();

	public StringHandler() {
		factories.put("null", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new NullStringInput<T>();
			}
		});

		factories.put("before", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new BeforeStringInput<T>(inputs[0], inputs[1]);
			}
		});

		factories.put("after", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new AfterStringInput<T>(inputs[0], inputs[1]);
			}
		});

		factories.put("clean", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new CleanStringInput<T>(inputs[0]);
			}
		});

		factories.put("ifnull", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				IfNullStringInput<T> input = new IfNullStringInput<T>();
				for (StringInput<T> in : inputs) {
					input.add(in);
				}
				return input;
			}
		});

		factories.put("bitcompose", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new BitComposeStringInput<T>(inputs[0], inputs[1], inputs[2]);
			}
		});

		factories.put("compositeprefix", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new CompositePrefixStringInput<T>(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5]);
			}
		});
		factories.put("compositespecification", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new CompositeSpecificationStringInput<T>(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5]);
			}
		});
		factories.put("compositevalue", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new CompositeValueStringInput<T>(inputs[0], inputs[1], inputs[2], inputs[3], inputs[4], inputs[5], inputs[6]);
			}
		});

		factories.put("removeprefix", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new RemovePrefixStringInput<T>(inputs[0], inputs[1]);
			}
		});
		factories.put("removesuffix", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new RemoveSuffixStringInput<T>(inputs[0], inputs[1]);
			}
		});
		factories.put("getprefix", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new GetPrefixStringInput<T>(inputs[0], inputs[1]);
			}
		});
		factories.put("getsuffix", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new GetSuffixStringInput<T>(inputs[0], inputs[1]);
			}
		});

		factories.put("find", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new FindStringInput<T>(inputs[0], inputs[1], inputs[2]);
			}
		});

		factories.put("replace", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new ReplaceStringInput<T>(inputs[0], inputs[1], inputs[2]);
			}
		});

		factories.put("ifeq", new StringInputFactory<T>() {
			@Override
			public StringInput<T> build(StringInput<T>[] inputs) {
				return new IfEqStringInput<T>(inputs[0], inputs[1], inputs[2], inputs[3]);
			}
		});
	}

	public StringHandler<T> add(String keyword, StringInputFactory<T> factory) {
		factories.put(keyword, factory);
		return this;
	}

	private StringInput<T> buildStringInput(String s) {
		int c = find(true, s, ':', 1); // 1 for '{'
		String keyword;
		String remaining;
		if (c < 0) {
			keyword = s.substring(1, s.length() - 1); // -1 for '}'
			remaining = null;
		} else {
			keyword = s.substring(1, c);
			remaining = s.substring(c + 1, s.length() - 1); // -1 for '}'
		}

		Deque<StringInput<T>> l = new LinkedList<StringInput<T>>();
		if (remaining != null) {
			int i = 0;
			while (true) {
				int d = find(true, remaining, ':', i);
				if (d < 0) {
					l.add(buildCompleteStringInput(remaining.substring(i)));
					break;
				}
				l.add(buildCompleteStringInput(remaining.substring(i, d)));
				i = d + 1;
			}
		}

		final StringInput<T> keywordInput = buildCompleteStringInput(keyword);

		@SuppressWarnings("unchecked")
		final StringInput<T>[] inputs = (StringInput<T>[]) new StringInput<?>[l.size()];
		l.toArray(inputs);

		return new StringInput<T>() {
			@Override
			public String get(T h) {
				String kw = keywordInput.get(h);
				StringInputFactory<T> factory = factories.get(kw);
				if (factory == null) {
					throw new RuntimeException("Invalid keyword: " + kw);
				}
				StringInput<T> input = factory.build(inputs);
				if (input == null) {
					throw new RuntimeException("Invalid factory: " + kw);
				}
				return input.get(h);
			}
		};
	}

	private StringInput<T> buildCompleteStringInput(String s) {
		AppendStringInput<T> inputs = new AppendStringInput<T>();
		int last = 0;
		while (true) {
			int a = find(false, s, '{', last);
			if (a < 0) {
				if (last < s.length()) {
					inputs.add(new EscapingStringInput<T>(s.substring(last)));
				}
				break;
			}
			if (last < a) {
				inputs.add(new EscapingStringInput<T>(s.substring(last, a)));
			}
			int b = find(true, s, '}', a + 1);
			inputs.add(buildStringInput(s.substring(a, b + 1)));
			last = b + 1;
		}
		return inputs;
	}

	public StringInput<T> build(String configuration) {
		return buildCompleteStringInput(configuration);
	}
}


package com.davfx.ninio.http;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Deque;
import java.util.LinkedList;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import com.davfx.ninio.core.Failing;
import com.davfx.ninio.http.dependencies.Dependencies;
import com.davfx.ninio.util.ConfigUtils;
import com.typesafe.config.Config;

final class GzipReader implements HttpContentReceiver {

	private static final Config CONFIG = ConfigUtils.load(new Dependencies()).getConfig(GzipReader.class.getPackage().getName());
	private static final int BUFFER_SIZE = CONFIG.getBytes("gzip.buffer").intValue();

	static final int GZIP_MAGIC = 0x8b1f;

//	private static final int FTEXT = 1; // Extra text
	private static final int FHCRC = 2; // Header CRC
	private static final int FEXTRA = 4; // Extra field
	private static final int FNAME = 8; // File name
	private static final int FCOMMENT = 16; // File comment

	private final Inflater inflater = new Inflater(true);
    private final CRC32 crc = new CRC32();
    private final ByteBuffer header = ByteBuffer.allocate(10);
    private final ByteBuffer extraCountBuffer = ByteBuffer.allocate(2);
    private boolean headerRead = false;
    private boolean extraCountRead = false;
    private int extraCount;
    private int extraSkipped = 0;
    private boolean nameSkipped = false;
    private boolean commentSkipped = false;
    private int headerCrcSkipped = 2;

    private static final int FOOTER_LENGTH = 8;

    private final Deque<ByteBuffer> previewFooter = new LinkedList<>();
    private int currentPreviewFooterLength = 0;

	private final Failing failing;
	private final HttpContentReceiver wrappee;

	private boolean ended = false;

	public GzipReader(Failing failing, HttpContentReceiver wrappee) {
		this.failing = failing;
		this.wrappee = wrappee;
	}

	// MUST sync-ly consume buffer
	@Override
	public void received(ByteBuffer deflated) {
		if (ended) {
			throw new IllegalStateException();
		}

		if (deflated.remaining() >= FOOTER_LENGTH) {
			for (ByteBuffer b : previewFooter) {
				if (!read(b)) {
					return;
				}
			}
			previewFooter.clear();
			currentPreviewFooterLength = 0;

			ByteBuffer deflatedKeepingFooter = deflated.duplicate();
			deflatedKeepingFooter.limit(deflatedKeepingFooter.limit() - FOOTER_LENGTH);
			deflated.position(deflated.position() + deflatedKeepingFooter.remaining());
			if (!read(deflatedKeepingFooter)) {
				return;
			}

			currentPreviewFooterLength += deflated.remaining();
			previewFooter.addLast(deflated.duplicate());
			deflated.position(deflated.position() + deflated.remaining());
			return;
		} else {
			currentPreviewFooterLength += deflated.remaining();
			previewFooter.addLast(deflated.duplicate());
			deflated.position(deflated.position() + deflated.remaining());

			int toFlush = FOOTER_LENGTH - currentPreviewFooterLength;
			while (toFlush > 0) {
				ByteBuffer b = previewFooter.getFirst();

				ByteBuffer d = b.duplicate();
				d.limit(Math.min(d.limit(), toFlush));
				b.position(b.position() + d.remaining());
				toFlush -= d.remaining();
				if (!read(d)) {
					return;
				}
				if (!b.hasRemaining()) {
					previewFooter.removeFirst();
				}
			}
		}
	}

    private boolean read(ByteBuffer deflated) {
		if (!headerRead) {
			while (header.hasRemaining()) {
				if (!deflated.hasRemaining()) {
					return true;
				}
				header.put(deflated.get());
			}
			header.flip();
			header.order(ByteOrder.LITTLE_ENDIAN);
			if ((header.getShort() & 0xFFFF) != GZIP_MAGIC) {
				ended = true;
				failing.failed(new IOException("Invalid Gzip magic number"));
				return false;
			}
			if ((header.get() & 0xFF) != Deflater.DEFLATED) {
				ended = true;
				failing.failed(new IOException("Invalid Gzip method"));
				return false;
			}
			int flags = header.get() & 0xFF;
			if ((flags & FEXTRA) == 0) {
				extraCountRead = true;
				extraCount = 0;
			}
			// Skip optional file name
			if ((flags & FNAME) == 0) {
				nameSkipped = true;
			}
			// Skip optional file comment
			if ((flags & FCOMMENT) == 0) {
				commentSkipped = true;
			}
			// Check optional header CRC
			if ((flags & FHCRC) == 0) {
				headerCrcSkipped = 2;
			}
			// 6 bytes skipped
			headerRead = true;
		}
		if (!extraCountRead) {
			while (extraCountBuffer.hasRemaining()) {
				if (!deflated.hasRemaining()) {
					return true;
				}
				extraCountBuffer.put(deflated.get());
			}
			extraCountBuffer.flip();
			extraCountBuffer.order(ByteOrder.LITTLE_ENDIAN);
			extraCount = header.getShort() & 0xFFFF;
		}
		while (extraSkipped < extraCount) {
			if (!deflated.hasRemaining()) {
				return true;
			}
			deflated.get();
			extraSkipped++;
		}
		while (!nameSkipped) {
			if (!deflated.hasRemaining()) {
				return true;
			}
			if (deflated.get() == 0) {
				nameSkipped = true;
			}
		}
		while (!commentSkipped) {
			if (!deflated.hasRemaining()) {
				return true;
			}
			if (deflated.get() == 0) {
				commentSkipped = true;
			}
		}
		while (headerCrcSkipped < 2) {
			if (!deflated.hasRemaining()) {
				return true;
			}
			deflated.get();
			headerCrcSkipped++;
		}

		if (deflated.hasRemaining()) {
			inflater.setInput(deflated.array(), deflated.arrayOffset() + deflated.position(), deflated.remaining());
			deflated.position(deflated.position() + deflated.remaining());

			while (true) { //!inflater.needsInput() && !inflater.finished()) {
				ByteBuffer inflated = ByteBuffer.allocate(BUFFER_SIZE);
				try {
					int c = inflater.inflate(inflated.array(), inflated.arrayOffset() + inflated.position(), inflated.remaining());
					if (c == 0) {
						break;
					}
					crc.update(inflated.array(), inflated.arrayOffset() + inflated.position(), c);
					inflated.position(inflated.position() + c);
				} catch (DataFormatException e) {
					ended = true;
					failing.failed(new IOException("Could not inflate", e));
					return false;
				}
				inflated.flip();
				wrappee.received(inflated);
			}
		}

		return true;
    }

    @Override
    public void ended() {
		if (ended) {
			throw new IllegalStateException();
		}
    	if (currentPreviewFooterLength < FOOTER_LENGTH) {
    		ended = true;
    		failing.failed(new IOException("Footer too short, missing " + (FOOTER_LENGTH - currentPreviewFooterLength) + " bytes"));
    		return;
    	}

    	ByteBuffer b = ByteBuffer.allocate(FOOTER_LENGTH);
    	for (ByteBuffer d : previewFooter) {
    		if (b.hasRemaining()) {
    			b.put(d);
    		}
    	}
    	b.flip();
		b.order(ByteOrder.LITTLE_ENDIAN);
		if ((b.getInt() & 0xFFFFFFFFL) != crc.getValue()) {
			ended = true;
			failing.failed(new IOException("Bad CRC"));
    		return;
		}
		if ((b.getInt() & 0xFFFFFFFFL) != inflater.getBytesWritten()) {
			ended = true;
			failing.failed(new IOException("Bad length"));
    		return;
		}
		ended = true;
		wrappee.ended();
	}
}


package se.cgbystrom.netty.thrift;

import java.util.Arrays;
import java.util.concurrent.BlockingQueue;

import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;

/**
 * Bridge between Netty and the client-side TTransport.
 *

 * Usage example:
 *

 *

 * {@Code
 * // create the client bootstrap
 * ChannelFactory factory = new NioClientSocketChannelFactory(Executors
 *      .newCachedThreadPool(), Executors.newCachedThreadPool(), 3);
 * ClientBootstrap bootstrap = new ClientBootstrap(factory);
 *

 * // create the netty handlers, and connect to the server
 * ThriftClientHandler handler = new ThriftClientHandler();
 *

 * // comment the following line and uncomment the next to use an Http Client. Needs an http-enabled server.
 * ChannelPipelineFactory factory = new ThriftPipelineFactory(handler);
 * // ChannelPipelineFactory factory = new ThriftHttpClientPipelineFactory(handler, "127.0.0.1", 8080);
 *

 * bootstrap.setPipelineFactory(factory);
 * Channel channel = bootstrap.connect(
 *         new InetSocketAddress("localhost", 8080)).awaitUninterruptibly()
 *         .getChannel();
 *

 * // create your thrift client and use it as normal
 * TTransport transport = new TNettyTransport(channel, handler);
 * try {
 *     TProtocol protocol = new TBinaryProtocol(transport);
 *     user-generated.thrift.Client client = new user-generated.thrift.Client(protocol);
 *     client.ping();
 * } catch (Exception exc) {
 *     exc.printStackTrace();
 * }
 *

 * // Release the netty resources
 * channel.close().awaitUninterruptibly();
 * bootstrap.releaseExternalResources();
 * }
 *

 *
 * @author Davide Inglima <limacat@gmail.com>
 */
public class TNettyTransport extends TTransport {

    private final BlockingQueue<Byte> input;

    private final Channel channel;

    private final ChannelBuffer output = ChannelBuffers.dynamicBuffer();

    public TNettyTransport(final Channel channel, final ThriftClientHandler remote) {
        this.channel = channel;
        this.input = remote.getOutputQueue();
    }

    /** Channel is managed via the Netty API */
    @Override
    public void close() {
    }

    /** Returns the status of this channel */
    @Override
    public boolean isOpen() {
        return channel.isOpen();
    }

    /** Channel is managed via the Netty API. */
    @Override
    public void open() throws TTransportException {
        // the channel must be opened using Netty
    }

    /**
     * Reads from the shared input queue, blocking if there is not enough input
     * in the buffer. It's the only way we can emulate the synchronous nature of
     * TTransport using netty.
     */
    @Override
    public int read(final byte[] output, final int offset, final int length) throws TTransportException {
        int read = 0;
        int index = offset;
        int space = length - offset;
        while (space > 0) {
            byte aByte = readAsynchrously();
            output[index] = aByte;
            space--;
            index++;
            read++;
        }
        return read;
    }

    private byte readAsynchrously() {
        boolean interrupted = false;
        for (; ;) {
            try {
                byte aByte = input.take();
                if (interrupted) {
                    Thread.currentThread().interrupt();
                }
                return aByte;
            } catch (InterruptedException e) {
                interrupted = true;
            }
        }
    }

    /** We need data to be written and flushed at once. */
    @Override
    public void flush() throws TTransportException {
        ChannelBuffer flush = ChannelBuffers.dynamicBuffer();
        flush.writeBytes(output);
        channel.write(flush).awaitUninterruptibly();
    }

    @Override
    public void write(byte[] array, int offset, int length) throws TTransportException {
        output.writeBytes(Arrays.copyOfRange(array, offset, length));
    }
}


package se.cgbystrom.netty.http.websocket;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;

import java.net.URI;
import java.util.concurrent.Executors;

/**
 * A factory for creating WebSocket clients.
 * The entry point for creating and connecting a client.
 * Can and should be used to create multiple instances.
 *
 * @author Carl Byström
 */
public class WebSocketClientFactory {

    private NioClientSocketChannelFactory socketChannelFactory = new NioClientSocketChannelFactory(
            Executors.newCachedThreadPool(),
            Executors.newCachedThreadPool());

    /**
     * Create a new WebSocket client
     * @param url URL to connect to.
     * @param callback Callback interface to receive events
     * @return A WebSocket client. Call {@link WebSocketClient#connect()} to connect.
     */
    public WebSocketClient newClient(final URI url, final WebSocketCallback callback) {
        ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);

        String protocol = url.getScheme();
        if (!protocol.equals("ws") && !protocol.equals("wss")) {
            throw new IllegalArgumentException("Unsupported protocol: " + protocol);
        }

        final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, callback);

        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                ChannelPipeline pipeline = Channels.pipeline();
                pipeline.addLast("decoder", new HttpResponseDecoder());
                pipeline.addLast("encoder", new HttpRequestEncoder());
                pipeline.addLast("ws-handler", clientHandler);
                return pipeline;
            }
        });

        return clientHandler;
    }
}


package com.clockbyte.admobadapter.sampleapp.express;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.clockbyte.admobadapter.expressads.AdViewWrappingStrategyBase;
import com.clockbyte.admobadapter.expressads.AdmobExpressAdapterWrapper;
import com.clockbyte.admobadapter.sampleapp.R;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.NativeExpressAdView;

import java.util.ArrayList;

/**
 *
 * @deprecated Use banners instead
 */
@Deprecated
public class MainActivity_ListView_Express extends Activity {

    ListView lvMessages;
    AdmobExpressAdapterWrapper adapterWrapper;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_listview);

        //highly-recommended in Firebase docs to initialize things early as possible
        //test_admob_app_id is different with unit_id! you could get it in your Admob console
        MobileAds.initialize(getApplicationContext(), getString(R.string.test_admob_app_id));

        initListViewItems();
    }

    /**
     * Inits an adapter with items, wrapping your adapter with a {@link AdmobExpressAdapterWrapper} and setting the listview to this wrapper
     * FIRST OF ALL Please notice that the following code will work on a real devices but emulator!
     */
    private void initListViewItems() {
        lvMessages = (ListView) findViewById(R.id.lvMessages);

        //creating your adapter, it could be a custom adapter as well
        ArrayAdapter<String> adapter  = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1);

        //your test devices' ids
        String[] testDevicesIds = new String[]{getString(R.string.testDeviceID),AdRequest.DEVICE_ID_EMULATOR};
        //when you'll be ready for release please use another ctor with admobReleaseUnitId instead.
        adapterWrapper = AdmobExpressAdapterWrapper.builder(this)
                .setLimitOfAds(10)
                .setFirstAdIndex(2)
                .setNoOfDataBetweenAds(10)
                .setTestDeviceIds(testDevicesIds)
                .setAdapter(adapter)
                .setAdViewWrappingStrategy(new AdViewWrappingStrategyBase() {
                    @NonNull
                    @Override
                    protected ViewGroup getAdViewWrapper(ViewGroup parent) {
                        return (ViewGroup) LayoutInflater.from(parent.getContext()).inflate(R.layout.web_ad_container,
                                parent, false);
                    }

                    @Override
                    protected void recycleAdViewWrapper(@NonNull ViewGroup wrapper, @NonNull NativeExpressAdView ad) {
                        //get the view which directly will contain ad
                        ViewGroup container = (ViewGroup) wrapper.findViewById(R.id.ad_container);
                        //iterating through all children of the container view and remove the first occured {@link NativeExpressAdView}. It could be different with {@param ad}!!!*//*
                        for (int i = 0; i < container.getChildCount(); i++) {
                            View v = container.getChildAt(i);
                            if (v instanceof NativeExpressAdView) {
                                container.removeViewAt(i);
                                break;
                            }
                        }
                    }

                    @Override
                    protected void addAdViewToWrapper(@NonNull ViewGroup wrapper, @NonNull NativeExpressAdView ad) {
                        //get the view which directly will contain ad
                        ViewGroup container = (ViewGroup) wrapper.findViewById(R.id.ad_container);
                        //add the {@param ad} directly to the end of container*//*
                        container.addView(ad);
                    }
                })
                .build();

        lvMessages.setAdapter(adapterWrapper); // setting an AdmobAdapterWrapper to a ListView

        //preparing the collection of data
        final String sItem = "item #";
        ArrayList<String> lst = new ArrayList<String>(100);
        for(int i=1;i<=100;i++)
            lst.add(sItem.concat(Integer.toString(i)));

        //adding a collection of data to your adapter and rising the data set changed event
        adapter.addAll(lst);
        adapter.notifyDataSetChanged();
    }

    /*
    * Seems to be a good practice to destroy all the resources you have used earlier :)
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        adapterWrapper.release();
    }
}


/*
 * Copyright (c) 2017 Clockbyte LLC. All rights reserved.
 *
 * 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 com.clockbyte.admobadapter.expressads;

import android.content.Context;
import android.database.DataSetObserver;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.BaseAdapter;
import android.widget.ListView;

import com.clockbyte.admobadapter.AdPreset;
import com.clockbyte.admobadapter.AdViewHelper;
import com.clockbyte.admobadapter.AdmobAdapterCalculator;
import com.clockbyte.admobadapter.AdmobFetcherBase;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.NativeExpressAdView;

import java.util.Collection;
import java.util.Collections;

/**
 * Adapter that has common functionality for any adapters that need to show ads in-between
 * other data.
 * @deprecated Use banners instead
 */
@Deprecated
public class AdmobExpressAdapterWrapper extends BaseAdapter implements AdmobFetcherBase.AdmobListener {

    private final String TAG = AdmobExpressAdapterWrapper.class.getCanonicalName();

    private BaseAdapter mAdapter;

    public BaseAdapter getAdapter() {
        return mAdapter;
    }

    private AdmobFetcherExpress adFetcher;
    private Context mContext;
    private AdmobAdapterCalculator AdapterCalculator = new AdmobAdapterCalculator();
    /*
    * Gets an object which incapsulates transformation of the source and ad blocks indices
    */
    public AdmobAdapterCalculator getAdapterCalculator(){return AdapterCalculator;}

    private AdViewWrappingStrategyBase AdViewWrappingStrategy = new AdViewWrappingStrategy();
    /**
     * Gets an object which incapsulates a wrapping logic for AdViews
     */
    public AdViewWrappingStrategyBase getAdViewWrappingStrategy(){return AdViewWrappingStrategy;}

    private static final int VIEW_TYPE_COUNT = 1;
    private static final int VIEW_TYPE_AD_EXPRESS = 0;

    private final static int DEFAULT_NO_OF_DATA_BETWEEN_ADS = 10;
    private final static int DEFAULT_LIMIT_OF_ADS = 3;

    /**
     * Gets the number of presets for ads that have been predefined by user (objects which contain adsize, unitIds etc).
     *
     * @return the number of ads that have been fetched
     */
    public int getAdPresetsCount(){
        return adFetcher!=null? this.adFetcher.getAdPresetsCount(): 0;
    }

    /**
     * Gets the number of ads that have been fetched so far.
     *
     * @return the number of ads that have been fetched
     */
    public int getFetchedAdsCount() {
        return adFetcher.getFetchedAdsCount();
    }

    /**
     * Gets the number of ads have been fetched so far + currently fetching ads
     *
     * @return the number of already fetched ads + currently fetching ads
     */
    public int getFetchingAdsCount(){
        return adFetcher.getFetchingAdsCount();
    }

    public int getViewTypeAdExpress(){
        return mAdapter.getViewTypeCount() + VIEW_TYPE_AD_EXPRESS;
    }

    /*
    * Gets the number of your data items between ad blocks, by default it equals to 10.
    * You should set it according to the Admob's policies and rules which says not to
    * display more than one ad block at the visible part of the screen
    * so you should choose this parameter carefully and according to your item's height and screen resolution of a target devices
    */
    public int getNoOfDataBetweenAds() {
        return AdapterCalculator.getNoOfDataBetweenAds();
    }
    /*
    * Sets the number of your data items between ad blocks, by default it equals to 10.
    * You should set it according to the Admob's policies and rules which says not to
    * display more than one ad block at the visible part of the screen
    * so you should choose this parameter carefully and according to your item's height and screen resolution of a target devices
    */
    public void setNoOfDataBetweenAds(int mNoOfDataBetweenAds) {
        AdapterCalculator.setNoOfDataBetweenAds(mNoOfDataBetweenAds);
    }

    public int getFirstAdIndex() {
        return AdapterCalculator.getFirstAdIndex();
    }
    /*
    * Sets the first ad block index (zero-based) in the adapter, by default it equals to 0
    */
    public void setFirstAdIndex(int firstAdIndex) {
        AdapterCalculator.setFirstAdIndex(firstAdIndex);
    }

    /*
    * Gets the max count of ad blocks per dataset, by default it equals to 3 (according to the Admob's policies and rules)
    */
    public int getLimitOfAds() {
        return AdapterCalculator.getLimitOfAds();
    }

    /*
    * Sets the max count of ad blocks per dataset, by default it equals to 3 (according to the Admob's policies and rules)
    */
    public void setLimitOfAds(int mLimitOfAds) {
        AdapterCalculator.setLimitOfAds(mLimitOfAds);
    }

    public AdmobExpressAdapterWrapper(){}

    public static Builder builder(@NonNull Context context) {
        return new AdmobExpressAdapterWrapper().new Builder(context);
    }

    public class Builder{
        private Builder(Context context){
            mContext = context;

            AdapterCalculator.setNoOfDataBetweenAds(DEFAULT_NO_OF_DATA_BETWEEN_ADS);
            AdapterCalculator.setLimitOfAds(DEFAULT_LIMIT_OF_ADS);

            adFetcher = new AdmobFetcherExpress(mContext);
            adFetcher.addListener(AdmobExpressAdapterWrapper.this);
        }

        /**
         * Sets the first ad block index (zero-based) in the adapter, by default it equals to 0
         */
        public Builder setFirstAdIndex(int firstAdIndex) {
            AdapterCalculator.setFirstAdIndex(firstAdIndex);
            return this;
        }

        /**
         * Sets the max count of ad blocks per dataset, by default it equals to 3 (according to the Admob's policies and rules)
         */
        public Builder setLimitOfAds(int mLimitOfAds) {
            AdapterCalculator.setLimitOfAds(mLimitOfAds);
            return this;
        }

        /**
         * Sets the number of your data items between ad blocks, by default it equals to 10.
         * You should set it according to the Admob's policies and rules which says not to
         * display more than one ad block at the visible part of the screen
         * so you should choose this parameter carefully and according to your item's height and screen resolution of a target devices
         */
        public Builder setNoOfDataBetweenAds(int mNoOfDataBetweenAds) {
            AdapterCalculator.setNoOfDataBetweenAds(mNoOfDataBetweenAds);
            return this;
        }

        /**
         * Sets a devices ID to test ads interaction.
         * You could pass null but it's better to set ids for all your test devices
         * including emulators. for emulator just use the
         * @see {AdRequest.DEVICE_ID_EMULATOR}
         */
        public Builder setTestDeviceIds(@NonNull String[] testDevicesId){
            adFetcher.getTestDeviceIds().clear();
            for (String testId: testDevicesId)
                adFetcher.addTestDeviceId(testId);
            return this;
        }

        /**
         * Sets a collection of ad presets ( object which contains unit ID and AdSize for banner).
         * It works like cycling FIFO (first in = first out, cycling from end to start).
         * Each ad block will get one from the queue.
         * If the desired count of ad blocks is greater than this collection size
         * then it will go again to the first item and iterate as much as it required.
         * ID should be active, please check it in your Admob's account.
         * Be careful: don't pass release unit id without setting the testDevicesId if you still haven't deployed a Release.
         * Otherwise your Admob account could be banned
         */
        public Builder setAdPresets(@NonNull Collection<AdPreset> adPresets){
            adFetcher.setAdPresets(adPresets);
            return this;
        }

        /**
         * Sets a single unit ID for each ad block.
         * If you are testing ads please don't set it to the release id
         */
        public Builder setSingleAdUnitId(@NonNull String admobUnitId){
            AdPreset adPreset = adFetcher.getAdPresetSingleOr(new AdPreset());
            adPreset.setAdUnitId(admobUnitId);
            adFetcher.setAdPresets(Collections.singletonList(adPreset));
            return this;
        }

        /**
         * Sets a single ad size for each ad block. By default it equals to AdSize(AdSize.FULL_WIDTH, 150);
         */
        public Builder setSingleAdSize(@NonNull AdSize adSize){
            AdPreset adPreset = adFetcher.getAdPresetSingleOr(new AdPreset(null, null));
            adPreset.setAdSize(adSize);
            adFetcher.setAdPresets(Collections.singletonList(adPreset));
            return this;
        }

        /**
         * Injects an object which incapsulates transformation of the source and ad blocks indices. You could override calculations
         * by inheritance from {@link AdmobAdapterCalculator} class
         */
        public Builder setAdapterCalculator(@NonNull AdmobAdapterCalculator adapterCalculator){
            AdapterCalculator = adapterCalculator;
            return setNoOfDataBetweenAds(DEFAULT_NO_OF_DATA_BETWEEN_ADS).setLimitOfAds(DEFAULT_LIMIT_OF_ADS);
        }

        /**
         * Injects an object which incapsulates a wrapping logic for AdViews. You could inject your own implementation
         * by inheritance from {@link AdViewWrappingStrategyBase} class
         */
        public Builder setAdViewWrappingStrategy(@NonNull AdViewWrappingStrategyBase adViewWrappingStrategy){
            AdViewWrappingStrategy = adViewWrappingStrategy;
            return this;
        }

        /**
         * Injects an object which incapsulates transformation of the source and ad blocks indices. You could override calculations
         * by inheritance of {@link AdmobAdapterCalculator} class
         */
        public Builder setAdapterCalculator(@NonNull AdmobAdapterCalculator adapterCalculator, int noOfDataBetweenAds, int limitOfAds, int firstAdIndex){
            AdapterCalculator = adapterCalculator;
            return setNoOfDataBetweenAds(noOfDataBetweenAds).setLimitOfAds(limitOfAds).setFirstAdIndex(firstAdIndex);
        }

        /**
         * Sets underlying adapter with your data collection.
         * If you want to inject your implementation of {@link AdmobAdapterCalculator} please set it before this call
         */
        public Builder setAdapter(BaseAdapter adapter) {
            mAdapter = adapter;
            mAdapter.registerDataSetObserver(new DataSetObserver() {
                @Override
                public void onChanged() {
                    notifyDataSetChanged();
                }

                @Override
                public void onInvalidated() {
                    notifyDataSetInvalidated();
                }
            });
            return this;
        }

        public AdmobExpressAdapterWrapper build(){
            if(adFetcher.getAdPresetsCount() == 0)
                adFetcher.setAdPresets(null);
            prefetchAds(AdmobFetcherExpress.PREFETCHED_ADS_SIZE);
            return AdmobExpressAdapterWrapper.this;
        }
    }

    /**
     * Creates N instances {@link NativeExpressAdView} from the next N taken instances {@link AdPreset}
     * Will start async prefetch of ad blocks to use its further
     * @return last created NativeExpressAdView
     */
    private NativeExpressAdView prefetchAds(int cntToPrefetch){
        NativeExpressAdView last = null;
        for (int i = 0; i < cntToPrefetch; i++){
            final NativeExpressAdView item = AdViewHelper.getExpressAdView(mContext, adFetcher.takeNextAdPreset());
            adFetcher.setupAd(item);
            //50 ms throttling to prevent a high-load of server
            new Handler(mContext.getMainLooper()).postDelayed(new Runnable() {
                @Override
                public void run() {
                    adFetcher.fetchAd(item);
                }
            }, 50 * i);
            last = item;
        }
        return last;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(getItemViewType(position) == getViewTypeAdExpress()) {
            int adPos = AdapterCalculator.getAdIndex(position);
            NativeExpressAdView ad = adFetcher.getAdForIndex(adPos);
            if (ad == null)
                ad = prefetchAds(1);
            if(convertView == null) {
                ViewGroup wrapper = AdViewWrappingStrategy.getAdViewWrapper(parent);
                //make sure the AdView for this position doesn't already have a parent of a different recycled NativeExpressHolder.
                if (ad.getParent() == null)
                    wrapper.addView(ad);
                return wrapper;
            }
            else{
                ViewGroup wrapper = (ViewGroup)convertView;
                AdViewWrappingStrategy.recycleAdViewWrapper(wrapper, ad);
                //make sure the AdView for this position doesn't already have a parent of a different recycled NativeExpressHolder.
                if (ad.getParent() != null)
                    ((ViewGroup) ad.getParent()).removeView(ad);
                AdViewWrappingStrategy.addAdViewToWrapper(wrapper, ad);
                return convertView;
            }
        }
        else{
            int origPos = AdapterCalculator.getOriginalContentPosition(position,
                    adFetcher.getFetchingAdsCount(), mAdapter.getCount());
            return mAdapter.getView(origPos, convertView, parent);
        }
    }

    /**
     *
Gets the count of all data, including interspersed ads.

     *

     *
If data size is 10 and an ad is to be showed after every 5 items starting at the index 0, this method
     * will return 12.

     *
     * @return the total number of items this adapter can show, including ads.
     * @see AdmobExpressAdapterWrapper#setNoOfDataBetweenAds(int)
     * @see AdmobExpressAdapterWrapper#getNoOfDataBetweenAds()
     */
    @Override
    public int getCount() {

        if (mAdapter != null) {
            /* Cnt of currently fetched ads, as long as it isn't more than no of max ads that can
            fit dataset. */
            int noOfAds = AdapterCalculator.getAdsCountToPublish(adFetcher.getFetchingAdsCount(), mAdapter.getCount());
            return mAdapter.getCount() > 0 ? mAdapter.getCount() + noOfAds : 0;
        } else {
            return 0;
        }
    }

    /**
     * Gets the item in a given position in the dataset. If an ad is to be returned,
     * a {@link NativeExpressAdView} object is returned.
     *
     * @param position the adapter position
     * @return the object or ad contained in this adapter position
     */
    @Override
    public Object getItem(int position) {
        if (AdapterCalculator.canShowAdAtPosition(position, adFetcher.getFetchingAdsCount())) {
            int adPos = AdapterCalculator.getAdIndex(position);
            return adFetcher.getAdForIndex(adPos);
        } else {
            int origPos = AdapterCalculator.getOriginalContentPosition(position,
                    adFetcher.getFetchingAdsCount(), mAdapter.getCount());
            return mAdapter.getItem(origPos);
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getViewTypeCount() {
        return VIEW_TYPE_COUNT + getAdapter().getViewTypeCount();
    }

    @Override
    public int getItemViewType(int position) {
        checkNeedFetchAd(position);
        if (AdapterCalculator.canShowAdAtPosition(position, adFetcher.getFetchingAdsCount())) {
            return getViewTypeAdExpress();
        } else {
            int origPos = AdapterCalculator.getOriginalContentPosition(position,
                    adFetcher.getFetchingAdsCount(), mAdapter.getCount());
            return mAdapter.getItemViewType(origPos);
        }
    }

    private void checkNeedFetchAd(int position){
        if(AdapterCalculator.hasToFetchAd(position, adFetcher.getFetchingAdsCount()))
            prefetchAds(1);
    }

    public void reinitialize() {
        adFetcher.destroyAllAds();
        prefetchAds(AdmobFetcherExpress.PREFETCHED_ADS_SIZE);
        notifyDataSetChanged();
    }

    /**
     * Free all resources, weak-refs
     */
    public void release(){
        adFetcher.release();
    }

    @Override
    public void onAdLoaded(int adIdx) {
        notifyDataSetChanged();
    }

    @Override
    public void onAdsCountChanged() {
        notifyDataSetChanged();
    }

    @Override
    public void onAdFailed(int adIdx, int errorCode, Object adPayload) {
        NativeExpressAdView adView = (NativeExpressAdView)adPayload;
        if (adView != null) {
            ViewParent parent = adView.getParent();
            if(parent == null || parent instanceof ListView)
                adView.setVisibility(View.GONE);
            else {
                while (parent.getParent() != null && !(parent.getParent() instanceof ListView))
                    parent = parent.getParent();
                ((View) parent).setVisibility(View.GONE);
            }
        }
        notifyDataSetChanged();
    }

}


/*
 * Copyright (c) 2017 Clockbyte LLC. All rights reserved.
 *
 * 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 com.clockbyte.admobadapter.bannerads;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Created by FILM on 02.10.2016.
 */

public class BannerAdPresetCyclingList extends ArrayList<BannerAdPreset> {

    private int currentIdx = -1;

    public BannerAdPresetCyclingList(){
        super();
    }

    public int getCurrentIdx() {
        return currentIdx;
    }

    /**
     * Gets next ad preset for Admob banners from FIFO.
     * It works like cycling FIFO (first in = first out, cycling from end to start).
     * Each ad block will get one from the queue.
     * If the desired count of ad blocks is greater than this collection size
     * then it will go again to the first item and iterate as much as it required.
     * ID should be active, please check it in your Admob's account.
     */
    public BannerAdPreset get() {
        if(size() == 0) return null;
        if (size() == 1) return get(0);
        currentIdx = ++currentIdx % size();
        return get(currentIdx);
    }

    /**
     * Tries to add an item to collection if it is valid {@link BannerAdPreset#isValid()}
     * @return true if item was added, false - otherwise
     */
    @Override
    public boolean add(BannerAdPreset expressAdPreset) {
        return !(expressAdPreset == null || !expressAdPreset.isValid())
                && super.add(expressAdPreset);
    }

    /**
     * Tries to add items to collection if valid {@link BannerAdPreset#isValid()}
     * @return true if items were added, false - otherwise
     */
    @Override
    public boolean addAll(Collection<? extends BannerAdPreset> c) {
        ArrayList<BannerAdPreset> lst = new ArrayList<>();
        for (BannerAdPreset eap : c) {
            if(eap!=null && eap.isValid())
                lst.add(eap);
        }
        return super.addAll(lst);
    }
}


/*
 * Copyright (c) 2017 Clockbyte LLC. All rights reserved.
 *
 * 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 Banner or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.clockbyte.admobadapter.bannerads;

import android.content.Context;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import com.clockbyte.admobadapter.AdViewHelper;
import com.clockbyte.admobadapter.AdapterWrapperObserver;
import com.clockbyte.admobadapter.AdmobAdapterCalculator;
import com.clockbyte.admobadapter.AdmobFetcherBase;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.AdView;

import java.util.Collection;
import java.util.Collections;

/**
 * Adapter that has common functionality for any adapters that need to show ads in-between
 * other data.
 */
public class AdmobBannerRecyclerAdapterWrapper
        extends RecyclerView.Adapter<RecyclerView.ViewHolder>
        implements AdmobFetcherBase.AdmobListener {

    private final String TAG = AdmobBannerRecyclerAdapterWrapper.class.getCanonicalName();

    private RecyclerView.Adapter<RecyclerView.ViewHolder> mAdapter;

    public RecyclerView.Adapter<RecyclerView.ViewHolder> getAdapter() {
        return mAdapter;
    }

    private AdmobFetcherBanner adFetcher;
    private Context mContext;
    private AdmobAdapterCalculator AdapterCalculator = new AdmobAdapterCalculator();
    /**
    * Gets an object which incapsulates transformation of the source and ad blocks indices
    */
    public AdmobAdapterCalculator getAdapterCalculator(){return AdapterCalculator;}

    private BannerAdViewWrappingStrategyBase AdViewWrappingStrategy = new BannerAdViewWrappingStrategy();
    /**
    * Gets an object which incapsulates a wrapping logic for AdViews
    */
    public BannerAdViewWrappingStrategyBase getAdViewWrappingStrategy(){return AdViewWrappingStrategy;}

    private static final int VIEW_TYPE_AD_BANNER = 0;

    private final static int DEFAULT_NO_OF_DATA_BETWEEN_ADS = 10;
    private final static int DEFAULT_LIMIT_OF_ADS = 3;
    private final static int DEFAULT_VIEWTYPE_SOURCE_MAX = 0;

    /**
     * Gets the number of presets for ads that have been predefined by user (objects which contain adsize, unitIds etc).
     *
     * @return the number of ads that have been fetched
     */
    public int getAdPresetsCount(){
        return adFetcher!=null? this.adFetcher.getAdPresetsCount(): 0;
    }

    /**
     * Gets the number of ads that have been fetched so far.
     *
     * @return the number of ads that have been fetched
     */
    public int getFetchedAdsCount() {
        return adFetcher.getFetchedAdsCount();
    }

    /**
     * Gets the number of ads have been fetched so far + currently fetching ads
     *
     * @return the number of already fetched ads + currently fetching ads
     */
    public int getFetchingAdsCount(){
        return adFetcher.getFetchingAdsCount();
    }

    public int getViewTypeAdBanner(){
        return getViewTypeBiggestSource() + VIEW_TYPE_AD_BANNER + 1;
    }

    /*
    * Gets the number of your data items between ad blocks, by default it equals to 10.
    * You should set it according to the Admob's policies and rules which says not to
    * display more than one ad block at the visible part of the screen
    * so you should choose this parameter carefully and according to your item's height and screen resolution of a target devices
    */
    public int getNoOfDataBetweenAds() {
        return AdapterCalculator.getNoOfDataBetweenAds();
    }

    /*
    * Sets the number of your data items between ad blocks, by default it equals to 10.
    * You should set it according to the Admob's policies and rules which says not to
    * display more than one ad block at the visible part of the screen
    * so you should choose this parameter carefully and according to your item's height and screen resolution of a target devices
    */
    public void setNoOfDataBetweenAds(int mNoOfDataBetweenAds) {
        AdapterCalculator.setNoOfDataBetweenAds(mNoOfDataBetweenAds);
    }

    public int getFirstAdIndex() {
        return AdapterCalculator.getFirstAdIndex();
    }

    /*
    * Sets the first ad block index (zero-based) in the adapter, by default it equals to 0
    */
    public void setFirstAdIndex(int firstAdIndex) {
        AdapterCalculator.setFirstAdIndex(firstAdIndex);
    }

    /*
    * Gets the max count of ad blocks per dataset, by default it equals to 3 (according to the Admob's policies and rules)
    */
    public int getLimitOfAds() {
        return AdapterCalculator.getLimitOfAds();
    }

    /*
    * Sets the max count of ad blocks per dataset, by default it equals to 3 (according to the Admob's policies and rules)
    */
    public void setLimitOfAds(int mLimitOfAds) {
        AdapterCalculator.setLimitOfAds(mLimitOfAds);
    }

    private int viewTypeBiggestSource;
    /*
   * Gets the biggest value among all the view types in the underlying source adapter, by default it equals to 0.
   */
    public int getViewTypeBiggestSource() {
        return viewTypeBiggestSource;
    }

    /*
    * Sets the biggest value among all the view types in the underlying source adapter, by default it equals to 0.
    */
    public void setViewTypeBiggestSource(int viewTypeBiggestSource) {
        this.viewTypeBiggestSource = viewTypeBiggestSource;
    }

    private AdmobBannerRecyclerAdapterWrapper(){}

    public static Builder builder(@NonNull Context context) {
        return new AdmobBannerRecyclerAdapterWrapper().new Builder(context);
    }

    public class Builder{
        private Builder(Context context){
            mContext = context;

            viewTypeBiggestSource = DEFAULT_VIEWTYPE_SOURCE_MAX;

            AdapterCalculator.setNoOfDataBetweenAds(DEFAULT_NO_OF_DATA_BETWEEN_ADS);
            AdapterCalculator.setLimitOfAds(DEFAULT_LIMIT_OF_ADS);

            adFetcher = new AdmobFetcherBanner(mContext);
            adFetcher.addListener(AdmobBannerRecyclerAdapterWrapper.this);
        }

        /**
        * Sets the first ad block index (zero-based) in the adapter, by default it equals to 0
        */
        public Builder setFirstAdIndex(int firstAdIndex) {
            AdapterCalculator.setFirstAdIndex(firstAdIndex);
            return this;
        }

        /**
        * Sets the max count of ad blocks per dataset, by default it equals to 3 (according to the Admob's policies and rules)
        */
        public Builder setLimitOfAds(int mLimitOfAds) {
            AdapterCalculator.setLimitOfAds(mLimitOfAds);
            return this;
        }

        /**
        * Sets the number of your data items between ad blocks, by default it equals to 10.
        * You should set it according to the Admob's policies and rules which says not to
        * display more than one ad block at the visible part of the screen
        * so you should choose this parameter carefully and according to your item's height and screen resolution of a target devices
        */
        public Builder setNoOfDataBetweenAds(int mNoOfDataBetweenAds) {
            AdapterCalculator.setNoOfDataBetweenAds(mNoOfDataBetweenAds);
            return this;
        }

        /**
         * Sets the biggest value among all the view types in the underlying source adapter, by default it equals to 0.
         */
        public Builder setViewTypeBiggestSource(int viewTypeBiggestSource) {
            AdmobBannerRecyclerAdapterWrapper.this.viewTypeBiggestSource = viewTypeBiggestSource;
            return this;
        }

        /**
         * Sets a devices ID to test ads interaction.
         * You could pass null but it's better to set ids for all your test devices
         * including emulators. for emulator just use the
         * @see {AdRequest.DEVICE_ID_EMULATOR}
         */
        public Builder setTestDeviceIds(@NonNull String[] testDevicesId){
            adFetcher.getTestDeviceIds().clear();
            for (String testId: testDevicesId)
                adFetcher.addTestDeviceId(testId);
            return this;
        }

        /**
         * Sets a collection of ad presets ( object which contains unit ID and AdSize for banner).
         * It works like cycling FIFO (first in = first out, cycling from end to start).
         * Each ad block will get one from the queue.
         * If the desired count of ad blocks is greater than this collection size
         * then it will go again to the first item and iterate as much as it required.
         * ID should be active, please check it in your Admob's account.
         * Be careful: don't pass release unit id without setting the testDevicesId if you still haven't deployed a Release.
         * Otherwise your Admob account could be banned
         */
        public Builder setAdPresets(@NonNull Collection<BannerAdPreset> adPresets){
            adFetcher.setAdPresets(adPresets);
            return this;
        }

        /**
         * Sets a single unit ID for each ad block.
         * If you are testing ads please don't set it to the release id
         */
        public Builder setSingleAdUnitId(@NonNull String admobUnitId){
            BannerAdPreset adPreset = adFetcher.getAdPresetSingleOr(new BannerAdPreset());
            adPreset.setAdUnitId(admobUnitId);
            adFetcher.setAdPresets(Collections.singletonList(adPreset));
            return this;
        }

        /**
         * Sets a single ad size for each ad block. By default it equals to AdSize(AdSize.FULL_WIDTH, 150);
         */
        public Builder setSingleAdSize(@NonNull AdSize adSize){
            BannerAdPreset adPreset = adFetcher.getAdPresetSingleOr(new BannerAdPreset(null, null));
            adPreset.setAdSize(adSize);
            adFetcher.setAdPresets(Collections.singletonList(adPreset));
            return this;
        }

        /**
        * Injects an object which incapsulates transformation of the source and ad blocks indices. You could override calculations
        * by inheritance from {@link AdmobAdapterCalculator} class
        */
        public Builder setAdapterCalculator(@NonNull AdmobAdapterCalculator adapterCalculator){
            AdapterCalculator = adapterCalculator;
            return setNoOfDataBetweenAds(DEFAULT_NO_OF_DATA_BETWEEN_ADS).setLimitOfAds(DEFAULT_LIMIT_OF_ADS);
        }

        /**
         * Injects an object which incapsulates a wrapping logic for AdViews. You could inject your own implementation
         * by inheritance from {@link BannerAdViewWrappingStrategyBase} class
         */
        public Builder setAdViewWrappingStrategy(@NonNull BannerAdViewWrappingStrategyBase adViewWrappingStrategy){
            AdViewWrappingStrategy = adViewWrappingStrategy;
            return this;
        }

        /**
         * Injects an object which incapsulates transformation of the source and ad blocks indices. You could override calculations
         * by inheritance of {@link AdmobAdapterCalculator} class
         */
        public Builder setAdapterCalculator(@NonNull AdmobAdapterCalculator adapterCalculator, int noOfDataBetweenAds, int limitOfAds, int firstAdIndex){
            AdapterCalculator = adapterCalculator;
            return setNoOfDataBetweenAds(noOfDataBetweenAds).setLimitOfAds(limitOfAds).setFirstAdIndex(firstAdIndex);
        }

        /**
         * Sets underlying adapter with your data collection.
         * If you want to inject your implementation of {@link AdmobAdapterCalculator} please set it before this call
         */
        public Builder setAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
            mAdapter = adapter;
            mAdapter.registerAdapterDataObserver(new AdapterWrapperObserver(AdmobBannerRecyclerAdapterWrapper.this, AdapterCalculator, adFetcher));
            notifyDataSetChanged();
            return this;
        }

        public AdmobBannerRecyclerAdapterWrapper build(){
            if(adFetcher.getAdPresetsCount() == 0)
                adFetcher.setAdPresets(null);
            prefetchAds(AdmobFetcherBanner.PREFETCHED_ADS_SIZE);
            return AdmobBannerRecyclerAdapterWrapper.this;
        }
    }

    /**
     * Creates N instances {@link AdView} from the next N taken instances {@link BannerAdPreset}
     * Will start async prefetch of ad blocks to use its further
     * @return last created banner AdView
     */
    private AdView prefetchAds(int cntToPrefetch){
        AdView last = null;
        for (int i = 0; i < cntToPrefetch; i++) {
            final AdView item = AdViewHelper.getBannerAdView(mContext, adFetcher.takeNextAdPreset());
            adFetcher.setupAd(item);
            //50 ms throttling to prevent a high-load of server
            new Handler(mContext.getMainLooper()).postDelayed(new Runnable() {
                @Override
                public void run() {
                    adFetcher.fetchAd(item);
                }
            }, 50 * i);
            last = item;
        }
        return last;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        if (viewHolder == null)
            return;
        if(viewHolder.getItemViewType() == getViewTypeAdBanner()) {
            BannerHolder bannerHolder = (BannerHolder) viewHolder;
            ViewGroup wrapper = bannerHolder.getAdViewWrapper();
            int adPos = AdapterCalculator.getAdIndex(position);
            AdView adView = adFetcher.getAdForIndex(adPos);
            if (adView == null)
                adView = prefetchAds(1);
            AdViewWrappingStrategy.recycleAdViewWrapper(wrapper, adView);
            //make sure the AdView for this position doesn't already have a parent of a different recycled BannerHolder.
            if (adView.getParent() != null)
                ((ViewGroup) adView.getParent()).removeView(adView);
            AdViewWrappingStrategy.addAdViewToWrapper(wrapper, adView);

        } else {
            int origPos = AdapterCalculator.getOriginalContentPosition(position,
                    adFetcher.getFetchingAdsCount(), mAdapter.getItemCount());
            mAdapter.onBindViewHolder(viewHolder, origPos);
        }
    }

    @Override
    public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == getViewTypeAdBanner()) {
            ViewGroup wrapper = AdViewWrappingStrategy.getAdViewWrapper(parent);
            return new BannerHolder(wrapper);
        }
        else{
            return mAdapter.onCreateViewHolder(parent, viewType);
        }
    }

    /**
     *
Gets the count of all data, including interspersed ads.

     *

     *
If data size is 10 and an ad is to be showed after every 5 items starting at the index 0, this method
     * will return 12.

     *
     * @return the total number of items this adapter can show, including ads.
     * @see AdmobBannerRecyclerAdapterWrapper#setNoOfDataBetweenAds(int)
     * @see AdmobBannerRecyclerAdapterWrapper#getNoOfDataBetweenAds()
     */
    @Override
    public int getItemCount() {

        if (mAdapter != null) {
            /*No of currently fetched ads, as long as it isn't more than no of max ads that can
            fit dataset.*/
            int noOfAds = AdapterCalculator.getAdsCountToPublish(adFetcher.getFetchingAdsCount(), mAdapter.getItemCount());
            return mAdapter.getItemCount() > 0 ? mAdapter.getItemCount() + noOfAds : 0;
        } else {
            return 0;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getItemViewType(int position) {
        checkNeedFetchAd(position);
        if (AdapterCalculator.canShowAdAtPosition(position, adFetcher.getFetchingAdsCount())) {
            return getViewTypeAdBanner();
        } else {
            int origPos = AdapterCalculator.getOriginalContentPosition(position,
                    adFetcher.getFetchingAdsCount(), mAdapter.getItemCount());
            return mAdapter.getItemViewType(origPos);
        }
    }

    private void checkNeedFetchAd(int position){
        if(AdapterCalculator.hasToFetchAd(position, adFetcher.getFetchingAdsCount()))
            prefetchAds(1);
    }

    /**
     * Clears all currently displaying ads and reinits the list
     */
    public void reinitialize() {
        adFetcher.destroyAllAds();
        prefetchAds(AdmobFetcherBanner.PREFETCHED_ADS_SIZE);
        notifyDataSetChanged();
    }

    /**
     * Free all resources, weak-refs
     */
    public void release(){
        adFetcher.release();
    }

    /**
     * pause all ads
     */
    public void pauseAll(){
        adFetcher.pauseAll();
    }
    /**
     * resume all ads
     */
    public void resumeAll(){
        adFetcher.resumeAll();
    }

    @Override
    public void onAdLoaded(int adIdx) {
        //raise ad's neighbour item changed.
        // cheap, quick and dirty solution to avoid ad's redraw and flickering.
        int pos = getAdapterCalculator().translateAdToWrapperPosition(adIdx);
        notifyItemChanged(pos==0? 1: Math.max(0, pos-1));
    }

    @Override
    public void onAdsCountChanged() {
        notifyDataSetChanged();
    }

    @Override
    public void onAdFailed(int adIdx, int errorCode, Object adPayload) {
        AdView adView = (AdView) adPayload;
        if (adView != null) {
            ViewParent parent = adView.getParent();
            if(parent == null || parent instanceof RecyclerView)
                adView.setVisibility(View.GONE);
            else {
                while (parent.getParent() != null && !(parent.getParent() instanceof RecyclerView))
                    parent = parent.getParent();
                ((View) parent).setVisibility(View.GONE);
            }
        }
        int pos = getAdapterCalculator().translateAdToWrapperPosition(Math.max(adIdx,0));
        notifyItemRangeChanged(pos, pos+15);
    }

}


/*
 * Copyright (c) 2017 Yahoo Inc. All rights reserved.
 * Copyright (c) 2017 Clockbyte LLC. All rights reserved.
 *
 * 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 com.clockbyte.admobadapter;

import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;

import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdLoader;
import com.google.android.gms.ads.formats.NativeAd;
import com.google.android.gms.ads.formats.NativeAdOptions;
import com.google.android.gms.ads.formats.NativeAppInstallAd;
import com.google.android.gms.ads.formats.NativeContentAd;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

public class AdmobFetcher extends AdmobFetcherBase{

    private final String TAG = AdmobFetcher.class.getCanonicalName();

    /**
     * Maximum number of ads to prefetch.
     */
    public static final int PREFETCHED_ADS_SIZE = 2;
    /**
     * Maximum number of times to try fetch an ad after failed attempts.
     */
    private static final int MAX_FETCH_ATTEMPT = 4;

    private int mFetchingAdsCnt = 0;
    private AdLoader adLoader;
    private List<NativeAd> mPrefetchedAdList = new ArrayList<NativeAd>();
    private SparseArray adMapAtIndex = new SparseArray();

    private EnumSet<EAdType> adTypeToFetch = EnumSet.allOf(EAdType.class);
    private final List<String> mAdmobReleaseUnitIds = new ArrayList<>();

    /**
     * Gets enumset which sets which of ad types this Fetcher should load
     */
    public EnumSet<EAdType> getAdTypeToFetch() {
        return adTypeToFetch;
    }
    /**
     * Gets enumset which sets which of ad types this Fetcher should load
     */
    public void setAdTypeToFetch(EnumSet<EAdType> adTypeToFetch) {
        this.adTypeToFetch = adTypeToFetch;
    }

    /**
     * Gets native ad at a particular index in the fetched ads list.
     *
     * @param index the index of ad in the fetched ads list
     * @return the native ad in the list
     * @see #getFetchedAdsCount()
     */
    public synchronized NativeAd getAdForIndex(final int index) {
        NativeAd adNative = null;
        if(index >= 0)
            adNative = (NativeAd) adMapAtIndex.get(index);

        if (adNative == null && mPrefetchedAdList.size() > 0) {
            adNative = mPrefetchedAdList.remove(0);

            if (adNative != null) {
                adMapAtIndex.put(index, adNative);
            }
        }

        ensurePrefetchAmount(); // Make sure we have enough pre-fetched ads
        return adNative;
    }

    @Override
    public synchronized int getFetchingAdsCount() {
        return mFetchingAdsCnt;
    }

    /**
     * Fetches a new native ad.
     *
     * @param context the current context.
     * @see #destroyAllAds()
     */
    public synchronized void prefetchAds(Context context) {
        super.prefetchAds(context);
        setupAds();
        fetchAd();
    }

    /**
     * Destroys ads that have been fetched, that are still being fetched and removes all resource
     * references that this instance still has. This should only be called when the Activity that
     * is showing ads is closing, preferably from the {@link android.app.Activity#onDestroy()}.
     *

     * The converse of this call is {@link #prefetchAds(Context)}.
     */
    public synchronized void destroyAllAds() {
        mFetchingAdsCnt = 0;
        adMapAtIndex.clear();
        mPrefetchedAdList.clear();

        Log.i(TAG, "destroyAllAds adList " + adMapAtIndex.size() + " prefetched " +
                mPrefetchedAdList.size());

        super.destroyAllAds();
    }

    /**
     * Destroys all the ads in Map to refresh it with new one
     * */
    public synchronized void clearMapAds() {
          adMapAtIndex.clear();
        mFetchingAdsCnt = mPrefetchedAdList.size();
    }

    /**
     * Fetches a new native ad.
     */
    protected synchronized void fetchAd() {
        Context context = mContext.get();

        if (context != null) {
            Log.i(TAG, "Fetching Ad now");
            if(lockFetch.getAndSet(true))
                return;
            mFetchingAdsCnt++;
            adLoader.loadAd(getAdRequest()); //Fetching the ads item
        } else {
            mFetchFailCount++;
            Log.i(TAG, "Context is null, not fetching Ad");
        }
    }

    /**
     * Ensures that the necessary amount of prefetched native ads are available.
     */
    protected synchronized void ensurePrefetchAmount() {
        if (mPrefetchedAdList.size() < PREFETCHED_ADS_SIZE &&
                (mFetchFailCount < MAX_FETCH_ATTEMPT)) {
            fetchAd();
        }
    }

    /**
     * Determines if the native ad can be used.
     *
     * @param adNative the native ad object
     * @return true if the ad object can be used, false otherwise
     */
    private boolean canUseThisAd(NativeAd adNative) {
        if (adNative != null) {
            NativeAd.Image logoImage = null;
            CharSequence header = null, body = null;
            if (adNative instanceof NativeContentAd) {
                NativeContentAd ad = (NativeContentAd) adNative;
                logoImage = ad.getLogo();
                header = ad.getHeadline();
                body = ad.getBody();
            } else if (adNative instanceof NativeAppInstallAd) {
                NativeAppInstallAd ad = (NativeAppInstallAd) adNative;
                logoImage = ad.getIcon();
                header = ad.getHeadline();
                body = ad.getBody();
            }

            if (!TextUtils.isEmpty(header)
                    && !TextUtils.isEmpty(body)) {
                return true;
            }
        }
        return false;
    }

    public String getDefaultUnitId() {
        return mContext.get().getResources().getString(R.string.test_admob_unit_id);
    }

    private String getReleaseUnitId() {
        return mAdmobReleaseUnitIds.size() > 0 ? mAdmobReleaseUnitIds.get(0) : null;
    }

    /**
     * Subscribing to the native ads events
     */
    protected synchronized void setupAds() {
        String unitId = getReleaseUnitId() != null ? getReleaseUnitId() : getDefaultUnitId();
        AdLoader.Builder adloaderBuilder = new AdLoader.Builder(mContext.get(), unitId)
                .withAdListener(new AdListener() {
                    @Override
                    public void onAdFailedToLoad(int errorCode) {
                        // Handle the failure by logging, altering the UI, etc.
                        Log.i(TAG, "onAdFailedToLoad " + errorCode);
                        lockFetch.set(false);
                        mFetchFailCount++;
                        mFetchingAdsCnt--;
                        ensurePrefetchAmount();
                        onAdFailed( mPrefetchedAdList.size(), errorCode, null);
                    }
                })
                .withNativeAdOptions(new NativeAdOptions.Builder()
                        // Methods in the NativeAdOptions.Builder class can be
                        // used here to specify individual options settings.
                        .build());
        if(getAdTypeToFetch().contains(EAdType.ADVANCED_INSTALLAPP))
            adloaderBuilder.forAppInstallAd(new NativeAppInstallAd.OnAppInstallAdLoadedListener() {
                    @Override
                    public void onAppInstallAdLoaded(NativeAppInstallAd appInstallAd) {
                        onAdFetched(appInstallAd);
                    }
                });
        if(getAdTypeToFetch().contains(EAdType.ADVANCED_CONTENT))
            adloaderBuilder.forContentAd(new NativeContentAd.OnContentAdLoadedListener() {
                    @Override
                    public void onContentAdLoaded(NativeContentAd contentAd) {
                        onAdFetched(contentAd);
                    }
                });

        adLoader = adloaderBuilder.build();
    }

    /**
     * A handler for received native ads
     */
    private synchronized void onAdFetched(NativeAd adNative) {
        Log.i(TAG, "onAdFetched");
        int index = -1;
        if (canUseThisAd(adNative)) {
            mPrefetchedAdList.add(adNative);
            index = mPrefetchedAdList.size()-1;
            mNoOfFetchedAds++;
        }
        lockFetch.set(false);
        mFetchFailCount = 0;
        ensurePrefetchAmount();
        onAdLoaded(index);
    }

    public void setReleaseUnitIds(Collection<String> admobReleaseUnitIds) {
        if(admobReleaseUnitIds.size() > 1)
            throw new RuntimeException("Currently only supports one unit id.");

        mAdmobReleaseUnitIds.addAll(admobReleaseUnitIds);
    }
}


package com.clockbyte.admobadapter.sampleapp;

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.ArrayAdapter;

import com.clockbyte.admobadapter.AdmobRecyclerAdapterWrapper;
import com.clockbyte.admobadapter.ContentAdLayoutContext;
import com.clockbyte.admobadapter.EAdType;
import com.clockbyte.admobadapter.InstallAppAdLayoutContext;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.MobileAds;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity_RecyclerView extends Activity {

    RecyclerView rvMessages;
    AdmobRecyclerAdapterWrapper adapterWrapper;
    Timer updateAdsTimer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_recycleview);

        //highly-recommended in Firebase docs to initialize things early as possible
        //test_admob_app_id is different with unit_id! you could get it in your Admob console
        MobileAds.initialize(getApplicationContext(), getString(R.string.test_admob_app_id));

        initRecyclerViewItems();
        initUpdateAdsTimer();
    }

    /**
     * Inits an adapter with items, wrapping your adapter with a {@link AdmobRecyclerAdapterWrapper} and setting the recyclerview to this wrapper
     * FIRST OF ALL Please notice that the following code will work on a real devices but emulator!
     */
    private void initRecyclerViewItems() {
        rvMessages = (RecyclerView) findViewById(R.id.rvMessages);
        rvMessages.setLayoutManager(new LinearLayoutManager(this));

        //creating your adapter, it could be a custom adapter as well
        RecyclerExampleAdapter adapter  = new RecyclerExampleAdapter(this);

        //your test devices' ids
        String[] testDevicesIds = new String[]{getString(R.string.testDeviceID),AdRequest.DEVICE_ID_EMULATOR};
        //when you'll be ready for release please use another ctor with admobReleaseUnitId instead.
        adapterWrapper = new AdmobRecyclerAdapterWrapper(this, testDevicesIds);
        //By default both types of ads are loaded by wrapper.
        // To set which of them to show in the list you should use an appropriate ctor
        //adapterWrapper = new AdmobRecyclerAdapterWrapper(this, testDevicesIds, EnumSet.of(EAdType.ADVANCED_INSTALLAPP));

        //wrapping your adapter with a AdmobAdapterWrapper.
        adapterWrapper.setAdapter(adapter);
        //inject your custom layout and strategy of binding for installapp/content  ads
        //here you should pass the extended NativeAdLayoutContext
        //by default it has a value InstallAppAdLayoutContext.getDefault()
        //adapterWrapper.setInstallAdsLayoutContext(...);
        //by default it has a value ContentAdLayoutContext.getDefault()
        //adapterWrapper.setContentAdsLayoutContext(...);

        //Sets the max count of ad blocks per dataset, by default it equals to 3 (according to the Admob's policies and rules)
        adapterWrapper.setLimitOfAds(3);

        //Sets the number of your data items between ad blocks, by default it equals to 10.
        //You should set it according to the Admob's policies and rules which says not to
        //display more than one ad block at the visible part of the screen,
        // so you should choose this parameter carefully and according to your item's height and screen resolution of a target devices
        adapterWrapper.setNoOfDataBetweenAds(10);
        adapterWrapper.setFirstAdIndex(2);

        //if you use several view types in your source adapter then you have to set the biggest view type value with the following method
        //adapterWrapper.setViewTypeBiggestSource(100);

        rvMessages.setAdapter(adapterWrapper); // setting an AdmobRecyclerAdapterWrapper to a RecyclerView

        //preparing the collection of data
        final String sItem = "item #";
        ArrayList<String> lst = new ArrayList<String>(100);
        for(int i=1;i<=100;i++)
            lst.add(sItem.concat(Integer.toString(i)));

        //adding a collection of data to your adapter and rising the data set changed event
        adapter.addAll(lst);
        adapter.notifyDataSetChanged();
    }

    /*
    * Could be omitted. It's only for updating an ad blocks in each 60 seconds without refreshing the list
     */
    private void initUpdateAdsTimer() {
        updateAdsTimer = new Timer();
        updateAdsTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapterWrapper.requestUpdateAd();
                    }
                });
            }
        }, 60*1000, 60*1000);
    }

    /*
    * Seems to be a good practice to destroy all the resources you have used earlier :)
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(updateAdsTimer!=null)
            updateAdsTimer.cancel();
        adapterWrapper.destroyAds();
    }
}


/*
 * Copyright (c) 2017 Yahoo Inc. All rights reserved.
 * Copyright (c) 2017 Clockbyte LLC. All rights reserved.
 *
 * 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 com.clockbyte.admobadapter.bannerads;

import android.content.Context;
import android.os.Handler;
import android.util.Log;

import com.clockbyte.admobadapter.AdmobFetcherBase;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdView;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class AdmobFetcherBanner extends AdmobFetcherBase {

    private final String TAG = AdmobFetcherBanner.class.getCanonicalName();

    /**
     * Maximum number of ads to prefetch.
     */
    public static final int PREFETCHED_ADS_SIZE = 2;

    /**
     * Maximum number of times to try fetch an ad after failed attempts.
     */
    public static final int MAX_FETCH_ATTEMPT = 4;

    public AdmobFetcherBanner(Context context){
        super();
        mContext = new WeakReference<>(context);
    }

    private List<AdView> mPrefetchedAds = new ArrayList<>();
    private BannerAdPresetCyclingList mAdPresetCyclingList = new BannerAdPresetCyclingList();

    /**
     * Gets next ad preset for Admob banners from FIFO .
     * It works like cycling FIFO (first in = first out, cycling from end to start).
     * Each ad block will get one from the queue.
     * If the desired count of ad blocks is greater than this collection size
     * then it will go again to the first item and iterate as much as it required.
     * ID should be active, please check it in your Admob's account.
     */
    public BannerAdPreset takeNextAdPreset() {
        return this.mAdPresetCyclingList.get();
    }

    /**
     * Sets an unit IDs for admob banners.
     * It works like cycling FIFO (first in = first out, cycling from end to start).
     * Each ad block will get one from the queue.
     * If the desired count of ad blocks is greater than this collection size
     * then it will go again to the first item and iterate as much as it required.
     * ID should be active, please check it in your Admob's account.
     */
    public void setAdPresets(Collection<BannerAdPreset> adPresets) {
        Collection<BannerAdPreset> mAdPresets = adPresets==null||adPresets.size() == 0
                ? Collections.singletonList(BannerAdPreset.DEFAULT)
                :adPresets;
        mAdPresetCyclingList.clear();
        mAdPresetCyclingList.addAll(mAdPresets);
    }

    public int getAdPresetsCount(){
        return this.mAdPresetCyclingList.size();
    }

    public BannerAdPreset getAdPresetSingleOr(BannerAdPreset defaultValue){
        return this.mAdPresetCyclingList.size() == 1 ? this.mAdPresetCyclingList.get() : defaultValue;
    }

    /**
     * Gets banner ad at a particular index in the fetched ads list.
     *
     * @param adPos the index of ad in the fetched ads list
     * @return the banner ad in the list
     * @see #getFetchedAdsCount()
     */
    public synchronized AdView getAdForIndex(int adPos) {
        if(adPos >= 0 && mPrefetchedAds.size() > adPos)
            return mPrefetchedAds.get(adPos);
        return null;
    }

    /**
     * Fetches a new banner ad.
     */
    protected synchronized void fetchAd(final AdView adView) {
        if(mFetchFailCount > MAX_FETCH_ATTEMPT)
            return;

        Context context = mContext.get();

        if (context != null) {
            Log.i(TAG, "Fetching Ad now");
            new Handler(context.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    adView.loadAd(getAdRequest()); //Fetching the ads item
                }
            });
        } else {
            mFetchFailCount++;
            Log.i(TAG, "Context is null, not fetching Ad");
        }
    }

    /**
     * Subscribing to the banner ads events
     * @param adView
     */
    protected synchronized void setupAd(final AdView adView) {
        if(mFetchFailCount > MAX_FETCH_ATTEMPT)
            return;

        if(!mPrefetchedAds.contains(adView))
            mPrefetchedAds.add(adView);
        adView.setAdListener(new AdListener() {
            @Override
            public void onAdFailedToLoad(int errorCode) {
                super.onAdFailedToLoad(errorCode);
                // Handle the failure by logging, altering the UI, etc.
                onFailedToLoad(adView, errorCode);
            }
            @Override
            public void onAdLoaded() {
                super.onAdLoaded();
                onFetched(adView);
            }
        });
    }

    /**
     * A handler for received banner ads
     * @param adView
     */
    private synchronized void onFetched(AdView adView) {
        Log.i(TAG, "onAdFetched");
        mFetchFailCount = 0;
        mNoOfFetchedAds++;
        onAdLoaded(mNoOfFetchedAds - 1);
    }

    /**
     * A handler for failed banner ads
     * @param adView
     */
    private synchronized void onFailedToLoad(AdView adView, int errorCode) {
        Log.i(TAG, "onAdFailedToLoad " + errorCode);
        mFetchFailCount++;
        mNoOfFetchedAds = Math.max(mNoOfFetchedAds - 1, 0);
        //Since Fetch Ad is only called once without retries
        //hide ad row / rollback its count if still not added to list
        mPrefetchedAds.remove(adView);
        onAdFailed(mNoOfFetchedAds - 1, errorCode, adView);
    }

    @Override
    public synchronized int getFetchingAdsCount() {
        return mPrefetchedAds.size();
    }

    public synchronized void pauseAll(){
        for(AdView ad:mPrefetchedAds){
            ad.pause();
        }
    }

    public synchronized void resumeAll(){
        for(AdView ad:mPrefetchedAds){
            ad.resume();
        }
    }

    @Override
    public synchronized void destroyAllAds() {
        super.destroyAllAds();
        for(AdView ad:mPrefetchedAds){
            ad.destroy();
        }
        mPrefetchedAds.clear();
    }

    @Override
    public void release() {
        super.release();
        mAdPresetCyclingList.clear();
    }
}