content: /**
   * Copyright 2014 55 Minutes (http://www.55minutes.com)
   *
   * 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 fiftyfive.wicket.js;
  
  import java.nio.charset.Charset;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  import fiftyfive.wicket.js.locator.DefaultJavaScriptDependencyLocator;
  import fiftyfive.wicket.js.locator.JavaScriptDependencyLocator;
  import fiftyfive.wicket.js.locator.SearchLocation;
  import fiftyfive.wicket.js.locator.SprocketsParser;
  import fiftyfive.wicket.js.locator.SprocketsParserImplV4;
  import org.apache.wicket.Application;
  import org.apache.wicket.MetaDataKey;
  import org.apache.wicket.request.resource.PackageResourceReference;
  import org.apache.wicket.request.resource.ResourceReference;
  import org.apache.wicket.util.lang.Args;
  import org.apache.wicket.util.time.Duration;
  
  /**
   * Settings that affect how JavaScript dependencies are located. To obtain an 
   * instance, use the static {@link #get get()} method.
   * <p>
   * Most applications using out-of-the-box JavaScript widgets will be served
   * well by the default settings. Before changing the settings, review
   * the <a href="package-summary.html#dependency-resolution">dependency
   * resolution guide</a> to get a complete understanding of how
   * fiftyfive-wicket-js loads JavaScript files and determines their
   * dependencies.
   * <p>
   * Then consider these scenarios:
   * <ol>
   * <li><b>If you want to disable dependency resolution entirely,
   *     call {@link #setSprocketsParser setSprocketsParser(null)}.</b></li>
   * <li><b>If your application already has a mechanism for including jQuery
   *     and/or jQuery UI in the &lt;head&gt;</b> (e.g. via your base page),
   *     then you should take care that fiftyfive-wicket-js is not including its
   *     own copies of these libraries. In this scenario, call
   *     {@link #setJQueryResource setJQueryResource()} and
   *     {@link #setJQueryUIResource setJQueryUIResource()} with references to
   *     the files that your application is already using (or {@code null},
   *     if you don't want fiftyfive-wicket-js to manage these resources at all).
   *     </li>
   * <li><b>If you have your own library of custom JavaScript files</b>,
   *     consider placing those files inside your classpath, and then use
   *     {@link #addLibraryPath addLibraryPath()} to make fiftyfive-wicket-js
   *     aware of them. Then you can use the {@code //=require} dependency syntax
   *     or the single argument constructor for
   *     {@link JavaScriptDependency JavaScriptDependency}, and those files will
   *     be found automatically.</li>
   * <li><b>If you'd like to change the jQuery UI CSS theme</b>, call
   *     {@link #setJQueryUICSSResource setJQueryUICSSResource()} with a
   *     reference to the desired CSS file, or {@code null} if you don't want
   *     fiftyfive-wicket-js to manage this for you.</li>
   * </ol>
   * 
   * @since 2.0
   */
  public class JavaScriptDependencySettings
  {
      private static final MetaDataKey<JavaScriptDependencySettings> SETTINGS_KEY
          = new MetaDataKey<JavaScriptDependencySettings>() {};
      
      private Application app;
      private List<SearchLocation> locations;
      private ResourceReference jQueryResource;
      private ResourceReference jQueryUIResource;
      private ResourceReference jQueryUICSSResource;
      private Duration traversalCacheDuration;
      private String encoding;
      private JavaScriptDependencyLocator locator;
      private SprocketsParser sprocketsParser;
      
      /**
       * Returns the JavaScriptDependencySettings associated with the current
       * Wicket Application, creating a new default settings object if one does
       * not yet exist. This method can only be called within a Wicket thread.
       */
      public static JavaScriptDependencySettings get()
      {
          Application app = Application.get();
          if(null == app)
          {
              throw new IllegalStateException(
                  "No thread-local Wicket Application object was found. " +
                  "JavaScriptDependencySettings.get() can only be called " +
                  "within a Wicket request."
              );
          }
          JavaScriptDependencySettings settings = app.getMetaData(SETTINGS_KEY);
          if(null == settings)
          {
              settings = new JavaScriptDependencySettings(app);
              app.setMetaData(SETTINGS_KEY, settings);
          }
          return settings;
      }
      
      /**
       * Constructs a settings object for the given application with a reasonable
       * set of defaults.
       */
      protected JavaScriptDependencySettings(Application app)
      {
          super();
          this.app = app;
          this.locator = new DefaultJavaScriptDependencyLocator();
          this.sprocketsParser = new SprocketsParserImplV4();
  
          this.locations = new ArrayList<SearchLocation>();
  
          Class<?> c = JavaScriptDependencySettings.class;
          addLibraryPath(c, "");
          addLibraryPath(c, "lib");
          addLibraryPath(c, "lib/fiftyfive-utils");
          
          this.jQueryResource = new PackageResourceReference(
              JavaScriptDependencySettings.class,
              "lib/jquery.js"
          );
          this.jQueryUIResource = new PackageResourceReference(
              JavaScriptDependencySettings.class,
              "lib/jquery-ui.js"
          );
          this.jQueryUICSSResource = new PackageResourceReference(
              JavaScriptDependencySettings.class,
              "lib/jquery-ui/themes/redmond/jquery-ui.redmond.css"
          );
      }
      
      /**
       * Returns the JavaScriptDependencyLocator for this application.
       */
      public JavaScriptDependencyLocator getLocator()
      {
          return this.locator;
      }
      
      /**
       * Sets the JavaScriptDependencyLocator that should be used for this
       * application. By default this is
       * {@link DefaultJavaScriptDependencyLocator}.
       * 
       * @return {@code this} to allow chaining
       */
      public JavaScriptDependencySettings setLocator(JavaScriptDependencyLocator loc)
      {
          Args.notNull(loc, "loc");
          this.locator = loc;
          return this;
      }
      
      /**
       * Returns the list of classpath locations that will be used to find
       * JavaScript libraries.
       */
      public List<SearchLocation> getLibraryPaths()
      {
          return Collections.unmodifiableList(this.locations);
      }
  
      /**
       * Adds a classpath location to be used for locating JavaScript libraries.
       * A default list of locations from the fiftyfive-wicket-js JAR contains
       * jQuery, jQuery UI, cookies, fiftyfive-utils, jquery-scrollto and
       * strftime.
       * Call this method if you wish to add more libraries to this list, or if
       * you want your versions of the previously mentioned libraries to take
       * precedence. The paths that you add will be consulted before the
       * defaults.
       * <p>
       * Library paths are searched whenever you use the {@code //= require} syntax
       * for JavaScript dependencies, like this:
       * <pre class="example">
       * //= require libraryname</pre>
       * <p>
       * Or when you use
       * {@link JavaScriptDependency#JavaScriptDependency(String)} or
       * {@link MergedJavaScriptBuilder#addLibrary(String)}.
       * 
       * @return {@code this} to allow chaining
       */
      public JavaScriptDependencySettings addLibraryPath(Class<?> cls, String path)
      {
          this.locations.add(0, new SearchLocation(cls, path));
          return this;
      }
      
      /**
       * Returns the ResourceReference for the jQuery JavaScript file.
       */
      public ResourceReference getJQueryResource()
      {
          return this.jQueryResource;
      }
      
      /**
       * Sets the ResourceReference of the jQuery JavaScript file that should
       * be used whenever the dependency locator determines that jQuery is
       * needed. Pass {@code null} if you do not want the framework to include
       * jQuery automatically.
       * By default this refers to a copy of jQuery bundled in the
       * fiftyfive-wicket-js JAR.
       * 
       * @return {@code this} to allow chaining
       */
      public JavaScriptDependencySettings setJQueryResource(ResourceReference r)
      {
          this.jQueryResource = r;
          return this;
      }
      
      /**
       * Returns the ResourceReference for the jQuery UI JavaScript file.
       */
      public ResourceReference getJQueryUIResource()
      {
          return this.jQueryUIResource;
      }
  
      /**
       * Sets the ResourceReference of the jQuery UI JavaScript file that should
       * be used whenever the dependency locator determines that jQuery UI is
       * needed. Pass {@code null} if you do not want the framework to include
       * jQuery UI automatically.
       * By default this refers to a copy of jQuery UI bundled in the
       * fiftyfive-wicket-js JAR.
       * 
       * @return {@code this} to allow chaining
       */
      public JavaScriptDependencySettings setJQueryUIResource(ResourceReference r)
      {
          this.jQueryUIResource = r;
          return this;
      }
      
      /**
       * Returns the ResourceReference for the jQuery UI CSS theme file.
       */
      public ResourceReference getJQueryUICSSResource()
      {
          return this.jQueryUICSSResource;
      }
  
      /**
       * Sets the ResourceReference of the jQuery UI CSS theme file that should
       * be used whenever the dependency locator determines that jQuery UI is
       * needed. Pass {@code null} if you do not want the framework to include
       * jQuery UI CSS automatically.
       * By default this refers to a copy of jQuery UI "redmond" theme bundled
       * in the fiftyfive-wicket-js JAR.
       * 
       * @return {@code this} to allow chaining
       */
      public JavaScriptDependencySettings setJQueryUICSSResource(ResourceReference r)
      {
          this.jQueryUICSSResource = r;
          return this;
      }
      
      /**
       * Returns the strategy that will be used to parse Sprockets {@code //= require}
       * directives. The default is {@link SprocketsParserImplV4}.
       * @since 4.0
       */
      public SprocketsParser getSprocketsParser()
      {
          return this.sprocketsParser;
      }
      
      /**
       * Sets the strategy that will be used to parse Sprockets {@code //= require}
       * directives. The default strategy changed in fiftyfive-wicket-js 4.0. Starting
       * with 4.0 the default is {@link SprocketsParserImplV4}. If you would like to
       * restore the Sprockets behavior of earlier versions, pass an instance of
       * {@link fiftyfive.wicket.js.locator.SprocketsParserImplV3 SprocketsParserImplV3}.
       * <p>
       * The two versions differ in how they resolve paths specified in the
       * {@code //= require} directive. Refer to the documentation for each implementation
       * for further details.
       * <p>
       * Pass {@code null} to disable Sprockets parsing altogether.
       * 
       * @return {@code this} to allow chaining
       * @since 4.0
       */
      public JavaScriptDependencySettings setSprocketsParser(SprocketsParser p)
      {
          this.sprocketsParser = p;
          return this;
      }
      
      /**
       * Returns the duration that JavaScript dependency traversal results
       * (i.e. the tree of dependencies as determined by parsing sprockets
       * directives) are allowed to be stored in cache.
       */
      public Duration getTraversalCacheDuration()
      {
          // Interpret null based on application mode
          if(null == this.traversalCacheDuration)
          {
              if(this.app.usesDeploymentConfig())
              {
                  // Cache indefinitely
                  return Duration.MAXIMUM;
              }
              // Disable cache
              return Duration.NONE;
          }
          return this.traversalCacheDuration;
      }
  
      /**
       * Sets the duration that JavaScript dependency traversal results
       * (i.e. the tree of dependencies as determined by parsing sprockets
       * directives) are allowed to be stored in cache.
       * <p>
       * If this is set to {@code null} (the default), at runtime this will be
       * interpreted as a value of zero (cache disabled) if the application is
       * in development mode, and will be interpreted as the maximum duration
       * (effectively indefinite) if the application is in deployment mode.
       * 
       * @return {@code this} to allow chaining
       */
      public JavaScriptDependencySettings setTraversalCacheDuration(Duration d)
      {
          this.traversalCacheDuration = d;
          return this;
      }
      
      /**
       * Returns the character encoding that will be used when parsing
       * JavaScript files.
       */
      public String getEncoding()
      {
          if(null == this.encoding)
          {
              String enc = this.app.getMarkupSettings().getDefaultMarkupEncoding();
              if(null == enc)
              {
                  enc = Charset.defaultCharset().name();
              }
              return enc;
          }
          return this.encoding;
      }
  
      /**
       * Sets the character encoding that will be used when parsing
       * JavaScript files. If this is set to {@code null} (the default), then
       * Wicket's markup encoding setting will be used.
       * 
       * @return {@code this} to allow chaining
       * @see org.apache.wicket.settings.IMarkupSettings#getDefaultMarkupEncoding()
       */
      public JavaScriptDependencySettings setEncoding(String encoding)
      {
          this.encoding = encoding;
          return this;
      }
  }
  