All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rapidoid.io.Res Maven / Gradle / Ivy

The newest version!
package org.rapidoid.io;

import java.io.File;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

/*
 * #%L
 * rapidoid-io
 * %%
 * Copyright (C) 2014 - 2015 Nikolche Mihajlovski and contributors
 * %%
 * 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.
 * #L%
 */

@Authors("Nikolche Mihajlovski")
@Since("4.1.0")
public class Res {

	private static final ConcurrentMap, Res> FILES = U.concurrentMap();

	public static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(1);

	private final String shortName;

	private final Set filenames;

	private volatile byte[] bytes;

	private volatile long lastUpdatedOn;

	private volatile long lastModified;

	private volatile String content;

	private volatile boolean trackingChanges;

	private volatile String cachedFileName;

	private volatile Object attachment;

	private final Map changeListeners = U.synchronizedMap();

	private Res(String shortName, Set filenames) {
		this.shortName = shortName;
		this.filenames = filenames;
	}

	public static Res from(String filename) {
		return from(filename, true, filename);
	}

	public static Res from(File file) {
		return from(file.getAbsolutePath());
	}

	public static Res from(String shortName, boolean withDefaults, String... filenames) {
		Set fnames = U.set(filenames);

		if (withDefaults) {
			for (String filename : filenames) {
				fnames.add(IO.getDefaultFilename(filename));
			}
		}

		U.must(!fnames.isEmpty(), "Resource filename(s) must be specified!");

		Res cachedFile = FILES.get(fnames);

		if (cachedFile == null) {
			cachedFile = new Res(shortName, fnames);

			if (FILES.size() < 1000) {
				FILES.putIfAbsent(fnames, cachedFile);
			}
		}

		return cachedFile;
	}

	public synchronized byte[] getBytes() {
		loadResource();

		mustExist();
		return bytes;
	}

	public byte[] getBytesOrNull() {
		loadResource();
		return bytes;
	}

	protected void loadResource() {
		// micro-caching the file content, expires after 500ms
		if (U.time() - lastUpdatedOn >= 500) {
			boolean hasChanged = false;

			synchronized (this) {

				byte[] old = bytes;
				byte[] foundRes = null;

				for (String filename : filenames) {
					Log.trace("Trying to load the resource", "name", shortName, "file", filename);
					byte[] res = load(filename);
					if (res != null) {
						Log.trace("Loaded the resource", "name", shortName, "file", filename);
						foundRes = res;
						this.cachedFileName = filename;
						break;
					}
				}

				if (foundRes == null) {
					this.cachedFileName = null;
				}

				this.bytes = foundRes;
				hasChanged = !U.eq(old, bytes) && (old == null || bytes == null || !Arrays.equals(old, bytes));
				lastUpdatedOn = U.time();

				if (hasChanged) {
					content = null;
					attachment = null;
				}
			}

			if (hasChanged) {
				notifyChangeListeners();
			}
		}
	}

	protected byte[] load(String filename) {
		File file = IO.file(filename);

		if (file.exists()) {

			// a normal file on the file system
			Log.trace("Resource file exists", "name", shortName, "file", file);

			if (file.lastModified() > this.lastModified || !filename.equals(cachedFileName)) {
				Log.info("Loading resource file", "name", shortName, "file", file);
				this.lastModified = file.lastModified();
				return IO.loadBytes(filename);
			} else {
				Log.trace("Resource file not modified", "name", shortName, "file", file);
				return bytes;
			}
		} else {
			// it might not exist or it might be on the classpath or compressed in a JAR
			Log.trace("Trying to load classpath resource", "name", shortName, "file", file);
			return IO.loadBytes(filename);
		}
	}

	public synchronized String getContent() {
		mustExist();

		if (content == null) {
			byte[] b = getBytes();
			content = b != null ? new String(b) : null;
		}

		return content;
	}

	public boolean exists() {
		loadResource();
		return bytes != null;
	}

	public String getShortName() {
		return shortName;
	}

	@Override
	public String toString() {
		return "Res(" + shortName + ")";
	}

	public synchronized Reader getReader() {
		mustExist();
		return new StringReader(getContent());
	}

	public void mustExist() {
		U.must(exists(), "The file '%s' doesn't exist! Path: %s", shortName, filenames);
	}

	public Res onChange(Runnable listener) {
		return onChange("", listener);
	}

	public Res onChange(String name, Runnable listener) {
		changeListeners.put(name, listener);
		return this;
	}

	private void notifyChangeListeners() {
		if (!changeListeners.isEmpty()) {
			Log.info("Resource has changed, reloading...", "name", shortName);
		}

		for (Runnable listener : changeListeners.values()) {
			try {
				listener.run();
			} catch (Throwable e) {
				Log.error("Error while processing resource changes!", e);
			}
		}
	}

	public synchronized Res trackChanges() {
		if (!trackingChanges) {
			this.trackingChanges = true;
			EXECUTOR.scheduleWithFixedDelay(new Runnable() {
				@Override
				public void run() {
					// loading the resource causes the resource to check for changes
					loadResource();
				}
			}, 0, 300, TimeUnit.MILLISECONDS);
		}

		return this;
	}

	@SuppressWarnings("unchecked")
	public  T attachment() {
		return exists() ? (T) attachment : null;
	}

	public void attach(Object attachment) {
		this.attachment = attachment;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy