package org.smartly.commons.io.repository; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.*; /** * Provides common methods and fields for the default implementations of the * repository interface */ public abstract class AbstractRepository implements Repository { /** * Parent repository this repository is contained in. */ AbstractRepository parent; /** * Cache for direct child repositories */ Map<String, SoftReference<AbstractRepository>> repositories = Collections.synchronizedMap( new HashMap<String, SoftReference<AbstractRepository>>()); /** * Cache for direct resources */ Map<String, AbstractResource> resources = Collections.synchronizedMap(new HashMap<String, AbstractResource>()); /** * Cached name for faster access */ String path; /** * Cached short name for faster access */ String name; /** * Whether this repository uses an absolute path */ private boolean isAbsolute = false; /** * Called to create a child resource for this repository if it exists. * * @param name the name of the child resource * @return the child resource, or null if no resource with the given name exists * @throws java.io.IOException an I/O error occurred */ protected abstract Resource lookupResource(String name) throws IOException; /** * Create a new child reposiotory with the given name. * * @param name the name * @return the new child repository * @throws java.io.IOException an I/O error occurred */ protected abstract AbstractRepository createChildRepository(String name) throws IOException; /** * Add the repository's resources into the list, optionally descending into * nested repositories. * * @param list the list to add the resources to * @param recursive whether to descend into nested repositories * @throws java.io.IOException an I/O related error occurred */ protected abstract void getResources(List<Resource> list, boolean recursive) throws IOException; /** * Get the full name that identifies this repository globally */ public String getPath() { return path; } /** * Get the local name that identifies this repository locally within its * parent repository */ public String getName() { return name; } /** * Mark this repository as root repository. */ public void setRoot() { parent = null; } /** * Set this Repository to absolute mode. This will cause all its * relative path operations to use absolute paths instead. * * @param absolute true to operate in absolute mode */ public void setAbsolute(boolean absolute) { isAbsolute = absolute; } /** * Return true if this Repository is in absolute mode. * * @return true if absolute mode is on */ public boolean isAbsolute() { return isAbsolute; } /** * Get the path of this repository relative to its root repository. * * @return the repository path */ public String getRelativePath() { if (isAbsolute) { return path; } else if (parent == null) { return ""; } else { StringBuffer b = new StringBuffer(); getRelativePath(b); return b.toString(); } } private void getRelativePath(StringBuffer buffer) { if (isAbsolute) { buffer.append(path); } else if (parent != null) { parent.getRelativePath(buffer); buffer.append(name).append('/'); } } /** * Utility method to get the name for the module defined by this resource. * * @return the module name according to the securable module spec */ public String getModuleName() { return getRelativePath(); } /** * Resolve path relative to this repository. * * @param path a path string * @param absolute whether to return an absolute path that can be used without this repository * @return a List containing the path elements resolved relative to this repository */ protected String[] resolve(final String path, final boolean absolute) { final String[] elements = path.split(SEPARATOR); final LinkedList<String> list = new LinkedList<String>(); if (absolute) { list.addAll(Arrays.asList(this.path.split(SEPARATOR))); } for (String e : elements) { if ("..".equals(e)) { String last = list.isEmpty() ? null : list.getLast(); if (last == null || "..".equals(last)) { list.add(e); } else if (!isAbsolute || list.size() > 1) { list.removeLast(); } } else if (!".".equals(e) && !"".equals(e)) { list.add(e); } } return list.toArray(new String[list.size()]); } /** * Get a resource contained in this repository identified by the given local name. * If the name can't be resolved to a resource, a resource object is returned * for which {@link Resource exists()} returns <code>false<code>. */ public synchronized Resource getResource(final String subPath) throws IOException { final String[] list = this.resolve(subPath, false); AbstractRepository repo = this; if (list.length == 0) { repo = this.getParentRepository(); return repo == null ? null : repo.getResource(name); } else { String last = list[list.length - 1]; for (String e : list) { if (repo == null || e == last) { break; } repo = repo.lookupRepository(e); } return repo == null ? null : repo.lookupResource(last); } } /** * Get a child repository with the given name * * @param subpath the name of the repository * @return the child repository */ public AbstractRepository getChildRepository(String subpath) throws IOException { String[] list = resolve(subpath, false); AbstractRepository repo = this; for (String e : list) { if (repo == null) { return null; } repo = repo.lookupRepository(e); } return repo; } protected AbstractRepository lookupRepository(String name) throws IOException { if (".".equals(name) || "".equals(name)) { return this; } else if ("..".equals(name)) { return getParentRepository(); } SoftReference<AbstractRepository> ref = repositories.get(name); AbstractRepository repo = ref == null ? null : ref.get(); if (repo == null) { repo = createChildRepository(name); repositories.put(name, new SoftReference<AbstractRepository>(repo)); } return repo; } /** * Get this repository's parent repository. */ public AbstractRepository getParentRepository() { return parent; } /** * Get the repository's root repository */ public Repository getRootRepository() { if (parent == null) { return this; } return parent.getRootRepository(); } public Resource[] getResources() throws IOException { return getResources(false); } public Resource[] getResources(boolean recursive) throws IOException { final List<Resource> list = new ArrayList<Resource>(); this.getResources(list, recursive); return list.toArray(new Resource[list.size()]); } public Resource[] getResources(String resourcePath, boolean recursive) throws IOException { Repository repository = getChildRepository(resourcePath); if (repository == null || !repository.exists()) { return new Resource[0]; } return repository.getResources(recursive); } /** * Returns the repositories full path as string representation. * * @see #getPath() */ public String toString() { return getPath(); } // Optimized separator lookup to avoid object creation overhead // of StringTokenizer and friends on critical code private int findSeparator(final String path, final int start) { int max = path.length(); int numberOfSeparators = SEPARATOR.length(); int found = -1; for (int i = 0; i < numberOfSeparators; i++) { char c = SEPARATOR.charAt(i); for (int j = start; j < max; j++) { if (path.charAt(j) == c) { found = max = j; break; } } } return found; } }