/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.everrest.groovy; import groovy.lang.GroovyResourceLoader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; /** * @author andrew00x */ public class DefaultGroovyResourceLoader implements GroovyResourceLoader { private static final String DEFAULT_SOURCE_FILE_EXTENSION = ".groovy"; protected URL[] roots; private final int maxEntries = 200; protected final Map<String, URL> resources; final ConcurrentMap<String, FileNameLock> locks; @SuppressWarnings("serial") public DefaultGroovyResourceLoader(URL[] roots) throws MalformedURLException { this.roots = new URL[roots.length]; for (int i = 0; i < roots.length; i++) { String str = roots[i].toString(); if (str.charAt(str.length() - 1) != '/') { this.roots[i] = new URL(str + '/'); } else { this.roots[i] = roots[i]; } } resources = Collections.synchronizedMap(new LinkedHashMap<String, URL>(maxEntries + 1, 1.0f, true) { @Override protected boolean removeEldestEntry(Map.Entry<String, URL> eldest) { if (size() > maxEntries) { locks.remove(eldest.getKey()); return true; } return false; } }); locks = new ConcurrentHashMap<>(); } public DefaultGroovyResourceLoader(URL root) throws MalformedURLException { this(new URL[]{root}); } @Override public final URL loadGroovySource(String filename) throws MalformedURLException { String[] sourceFileExtensions = getSourceFileExtensions(); URL resource = null; filename = filename.replace('.', '/'); for (int i = 0; i < sourceFileExtensions.length && resource == null; i++) { resource = getResource(filename + sourceFileExtensions[i]); } return resource; } protected URL getResource(String filename) throws MalformedURLException { FileNameLock lock = locks.get(filename); if (lock == null) { FileNameLock newLock = new FileNameLock(); lock = locks.putIfAbsent(filename, newLock); if (lock == null) { lock = newLock; } } URL resource; synchronized (lock) { resource = resources.get(filename); final boolean inCache = resource != null; if (inCache && checkResource(resource)) { return resource; } resource = null; // Resource in cache is unreachable. for (int i = 0; i < roots.length && resource == null; i++) { URL tmp = createURL(roots[i], filename); if (checkResource(tmp)) { resource = tmp; } } if (resource != null) { resources.put(filename, resource); } else if (inCache) { resources.remove(filename); } } return resource; } protected URL createURL(URL root, String filename) throws MalformedURLException { return new URL(root, filename); } protected boolean checkResource(URL resource) { try { resource.openStream().close(); return true; } catch (IOException e) { return false; } } protected String[] getSourceFileExtensions() { return new String[]{DEFAULT_SOURCE_FILE_EXTENSION}; } private static final class FileNameLock { private static final AtomicInteger counter = new AtomicInteger(); private final int hash = counter.incrementAndGet(); @Override public int hashCode() { return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } return hash == ((FileNameLock)obj).hash; } } }