Java tutorial
/** * Copyright (c) 2013 Edgar Espina * * This file is part of amd4j (https://github.com/jknack/amd4j) * * 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.github.jknack.amd4j; import static org.apache.commons.io.FilenameUtils.getExtension; import static org.apache.commons.io.FilenameUtils.getPath; import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.Validate.notNull; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * Optimize a module by collecting all the dependencies and building a single file. * </p> * <p> * Basic Usage: * </p> * * <pre> * new Optimizer() * .optimize(new Config("myModule", "output.bundle.js")); * </pre> * * <p> * Registering module transformers: * </p> * * <pre> * new Optimizer() * .with(new TextTransformer()) * .optimize(new Config("myModule", "output.bundle.js")); * </pre> * * <p> * Using a {@link ResourceLoader}: * </p> * By default, resources are loaded from classpath, you can change that using a * {@link ResourceLoader}. * * <pre> * new Optimizer() * .with(new FileResourceLoader("baseDir")) * .optimize(new Config("myModule", "output.bundle.js")); * </pre> * * @author edgar.espina * @since 0.1.0 */ public class Amd4j { /** * Relative expression. */ private static final String RELATIVE_EXPRESSION = "./"; /** * The logging system. */ private final Logger logger = LoggerFactory.getLogger(getClass()); /** * The list of transformer to apply. */ private LinkedList<Transformer> transformers = new LinkedList<Transformer>(); /** * The resource loader. */ private ResourceLoader loader = new ClasspathResourceLoader(); /** * Append a new {@link Transformer}. * * @param transformer The transformer to append. Required. * @return This optimizer. */ public Amd4j with(final Transformer transformer) { transformers.add(notNull(transformer, "The transformer is required.")); return this; } /** * Set the resource loader to use. * * @param loader The resource loader. Required. * @return This optimizer. */ public Amd4j with(final ResourceLoader loader) { this.loader = notNull(loader, "The loader is required."); return this; } /** * Analyze a module by collecting all the dependencies. * * @param name The module name. Required. * @return A module and their dependencies. */ public Module analyze(final String name) { notNull(name, "The config is required."); return analyze(new Config(name.toString())); } /** * Analyze a module by collecting all the dependencies. * * @param config The configuration options. Required. * @return A module and their dependencies. */ public Module analyze(final Config config) { notNull(config, "The config is required."); logger.debug("Tracing dependencies for: {}\n", config.getName()); Module module = walk(config.getName(), config.getName(), config, new HashMap<ResourceURI, Module>()); return module; } /** * Merge all the dependencies into one single file, name anonymous modules and make AMD compatible * whose script that has a shim entry in the configuration options. * * @param config The configuration options. Required. * @return The module graph. */ public Module optimize(final Config config) { Module module = analyze(config); new Optimizer(config, transformers).walk(module); return module; } /** * Walk through a module and collect dependencies. * * @param modulePath The module's path. * @param moduleName The module's name. * @param config The configuration options. * @param registry The already processed modules. * @return A module or null if the module should be skipped. */ private Module walk(final String modulePath, final String moduleName, final Config config, final Map<ResourceURI, Module> registry) { try { String path = config.resolvePath(modulePath); if (Config.EMPTY.equals(path)) { logger.debug("skipped: {}", modulePath); return null; } ResourceURI uri = resolve(loader, ResourceURI.create(config.getBaseUrl(), path)); Module existing = registry.get(uri); if (existing != null) { logger.debug("included already: {}", modulePath); return existing; } String content = loader.load(uri); Module module = new Module(moduleName, uri, content); registry.put(uri, module); // collect dependencies Set<String> unresolvedDependencies = DependencyCollector.collect(config, module); for (String unresolved : unresolvedDependencies) { String dependencyName = unresolved.replace(RELATIVE_EXPRESSION, getPath(moduleName)); String dependencyPath = unresolved.replace(RELATIVE_EXPRESSION, getPath(path)); Module resolved = walk(dependencyPath, dependencyName, config, registry); if (resolved != null) { module.add(resolved); } } logger.debug("{}", uri); return module; } catch (AmdException ex) { LinkedList<String> path = new LinkedList<String>(); path.add(moduleName); path.addAll(ex.getPath()); AmdException rewriteEx = new AmdException(path, ex.getCause()); throw rewriteEx; } catch (Exception ex) { throw new AmdException(moduleName, ex); } } /** * Resolve a candidate uri to an existing uri. We need this bc, dependencies might or mightn't * have a file extension, or they might have a '.' in the file's name. * * @param loader The resource loader. * @param uri The candidate uri. * @return An existing uri for the candidate uri. * @throws IOException If the uri can't be resolved. */ private static ResourceURI resolve(final ResourceLoader loader, final ResourceURI uri) throws IOException { String path = uri.getPath(); LinkedList<ResourceURI> candidates = new LinkedList<ResourceURI>(); candidates.add(uri); ResourceURI alternative = ResourceURI.create(uri.toString() + ".js"); if (isEmpty(getExtension(path))) { candidates.addFirst(alternative); } else { candidates.addLast(alternative); } for (ResourceURI candidate : candidates) { if (loader.exists(candidate)) { return candidate; } } // force a file not found exception throw new FileNotFoundException(uri.toString()); } }