/* * The MIT License * * Copyright 2013 Tim Boudreau. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.mastfrog.giulius; import com.google.common.collect.Maps; import com.google.inject.Module; import com.mastfrog.guicy.annotations.Namespace; import com.mastfrog.settings.Settings; import com.mastfrog.settings.SettingsBuilder; import com.mastfrog.util.Checks; import com.mastfrog.util.ConfigurationError; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * Builder for Dependencies. Allows for adding Settings for various * namespaces programmatically. * * @author Tim Boudreau */ public final class DependenciesBuilder { private Map<String, List<SettingsBuilder>> settingsForNamespace = Maps.newHashMapWithExpectedSize(5); private List<Module> modules = new LinkedList<>(); private Set<File> locations = new LinkedHashSet<>(); private Set<SettingsBindings> settingsBindings = EnumSet.allOf(SettingsBindings.class); /** * Get the list of namespaces this DependenciesBuilder will bind settings * for. If you have called <code>addDefaultSettings()</code> this will * include any namespaces generated from annotations, which are defined * as the concatenation of all files named <code>com/mastfrog/namespaces.list</code> * on the classpath. * @return A set of namespaces */ public Set<String> namespaces() { return new HashSet<>(settingsForNamespace.keySet()); } /** * Add modules to be used when creating the Injector * @param modules Some modules * @return this */ public DependenciesBuilder add(Module... modules) { Checks.notNull("modules", modules); this.modules.addAll(Arrays.asList(modules)); return this; } /** * Add a folder on disk to look in for configuration files * * @param loc A folder on disk * @return */ public DependenciesBuilder addDefaultLocation(File loc) { if (loc.exists() && !loc.isDirectory()) { throw new ConfigurationError("Not a directory: " + loc); } locations.add(loc); return this; } /** * Disable binding of settings to some types if you know they * will not be used, to save (minimal) memory. This is only * significant if you are running in < 20Mb heap. * @param bindings The bindings to remove * @return this */ public DependenciesBuilder disableBindings(SettingsBindings... bindings) { EnumSet<SettingsBindings> toRemove = EnumSet.noneOf(SettingsBindings.class); for (SettingsBindings b : bindings) { toRemove.add(b); } settingsBindings.removeAll(toRemove); return this; } /** * Explicitly set the list of types that are bound to settings to * save (minimal) memory. * * @param bindings The types of bindings to set up * @return this */ public DependenciesBuilder enableOnlyBindingsFor(SettingsBindings... bindings) { EnumSet<SettingsBindings> newSet = EnumSet.noneOf(SettingsBindings.class); for (SettingsBindings b : bindings) { newSet.add(b); } this.settingsBindings.clear(); this.settingsBindings.addAll(newSet); return this; } private void addLocations(SettingsBuilder sb) { for (File loc : locations) { sb.addLocation(loc); } } /** * Add a namespace, causing all of the default locations to be used. * The namespace's settings will contain environment variables, system * properties and the contents of any generated-$NAMESPACE.properties and * $NAMESPACE.properties files in the default location on the classpath. * * @param name The name of the namespace. * @return this * @throws IOException */ public DependenciesBuilder addNamespace(String name) throws IOException { if (!settingsForNamespace.containsKey(name)) { File home = new File(System.getProperty("user.home")); File f = new File (home, name + SettingsBuilder.DEFAULT_EXTENSION); SettingsBuilder sb = new SettingsBuilder(name) .addEnv() .addSystemProperties() .addGeneratedDefaultsFromClasspath() .addDefaultsFromClasspath(); if (f.exists()) { sb.add(f); } addLocations(sb); add(sb.build(), name); } else { throw new IllegalArgumentException("Already added '" + name + "'"); } return this; } /** * Add the default settings (see SettingsBuilder.createDefault()), and * default settings for any namespaces found in /com/mastfrog/namespaces.list * files anywhere on the classpath (these are generated from the @Defaults * annotation). * * @return this * @throws IOException If loading settings fails */ public DependenciesBuilder addDefaultSettings() throws IOException { Set<String> namespaces = new HashSet<>(settingsForNamespace.keySet()); namespaces.addAll(Dependencies.loadNamespaceListsFromClasspath()); namespaces.add(Namespace.DEFAULT); for (String ns : namespaces) { Settings s; if (Namespace.DEFAULT.equals(ns)) { SettingsBuilder sb = new SettingsBuilder().addDefaultLocations(); addLocations(sb); s = sb.build(); } else { SettingsBuilder sb = new SettingsBuilder(ns).addDefaultLocations(); addLocations(sb); s = sb.build(); } add(s, ns); } return this; } public List<SettingsBuilder> getSettings(String ns) { return settingsForNamespace.get(ns); } /** * Add a Settings tied to a specific namespace. Note that if you have called * <code>addDefaultSettings()</code>, this will merge these settings with * any settings files for that namespace which are on the classpath. * * @param settings The settings * @param namespace The namespace, referenced by @Namespace annotations * on the related classes * @return this */ public DependenciesBuilder add(Settings settings, String namespace) { Checks.notNull("settings", settings); Checks.notNull("namespace", namespace); Checks.notEmpty("namespace", namespace); List<SettingsBuilder> l = settingsForNamespace.get(namespace); if (l == null) { l = new LinkedList<>(); settingsForNamespace.put(namespace, l); } SettingsBuilder sb = new SettingsBuilder(namespace).add(settings); l.add(sb); return this; } private boolean useMutableSettings; /** * If called, bind a MutableSettings (has setters) rather than the default * (mutable settings are bound, but are created on the fly and any changes * are not shared with other code). * @return this */ public DependenciesBuilder useMutableSettings() { useMutableSettings = true; return this; } private Map<String, Settings> collapse() throws IOException { Map<String, Settings> result = new HashMap<>(); for (Map.Entry<String, List<SettingsBuilder>> e : settingsForNamespace.entrySet()) { if (e.getValue().size() == 1) { result.put(e.getKey(), e.getValue().iterator().next().build()); } else { SettingsBuilder sb = new SettingsBuilder(e.getKey()); for (SettingsBuilder s : e.getValue()) { sb.add(s); } if (useMutableSettings) { result.put(e.getKey(), sb.buildMutableSettings()); } else { result.put(e.getKey(), sb.build()); } } } return result; } /** * Build a dependencies object. * * @return * @throws IOException */ public Dependencies build() throws IOException { return new Dependencies(collapse(), settingsBindings, modules.toArray(new Module[modules.size()])); } @Override public String toString() { StringBuilder sb = new StringBuilder("MODULES:\n"); for (Module m : modules) { if (sb.length() > 0) { sb.append(','); } sb.append(m).append('\n'); } sb.append("NAMESPACES:\n"); for (String ns : settingsForNamespace.keySet()) { sb.append(ns).append(','); } sb.append("\n"); for (String ns : settingsForNamespace.keySet()) { sb.append(ns).append(": ").append("\n").append (" ").append(settingsForNamespace.get(ns)).append("\n"); } return sb.toString(); } }