/**
* 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.locator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.lang.Args;
/**
* Holds a tree of JavaScript dependencies as they are traversed, ensuring
* that the ordering is properly maintained and duplicates are ignored. Once
* traversal is complete, the dependencies can be iterated in the order they
* should appear in the <head> by calling {@link #iterator iterator()}.
* <p>
* A property is also maintained to indicate whether CSS is needed by the
* JavaScript dependencies. Currently this is only used to indicate the need
* for jQuery UI's CSS theme, based on whether or not jQuery UI is a script
* dependency. An arbitrary number of CSS dependencies is not supported.
*
* @since 2.0
*/
public class DependencyCollection implements Iterable<ResourceReference>
{
private boolean frozen;
private int position;
private ResourceReference css;
private List<ResourceReference> resources;
/**
* Creates an empty collection.
*/
public DependencyCollection()
{
super();
this.frozen = false;
this.position = 0;
this.css = null;
this.resources = new ArrayList<ResourceReference>();
}
/**
* Adds a resource to the collection at the current level of the tree.
* The level of the tree can be adjusted by first calling
* {@link #descend descend()} and {@link #ascend ascend()}.
*
* @return {@code true} if the resource did not already exist in the
* collection, and was therefore added successfully; {@code false}
* if the resource already existed (or was {@code null})
* and was ignored.
*/
public boolean add(ResourceReference ref)
{
assertMutable();
if(null == ref || this.resources.contains(ref)) return false;
this.resources.add(this.position++, ref);
return true;
}
/**
* Inform the collection that subsequent calls to {@link #add add()}
* should be treated as descendents of the most recently added resource.
* Once all descendents have been added, {@link #ascend ascend()} should
* be called to move the insertion cursor back "up" the tree. Calls to
* {@code descend()} can be nested, as long as they are balanced with an
* equal number of calls to {@link #ascend ascend()}.
*/
public void descend()
{
assertMutable();
this.position--;
}
/**
* Inform the collection that we are finished adding descendent resources.
* Subsequent calls to {@link #add add()} will be treated as siblings, as
* opposed to children.
*
* @see #ascend
*/
public void ascend()
{
assertMutable();
this.position++;
}
/**
* Returns a reference to the jQuery UI CSS theme, if it is needed by
* one of the JavaScript dependencies. Otherwise returns {@code null}.
*/
public ResourceReference getCss()
{
return this.css;
}
/**
* Sets the CSS resource that should accompany these JavaScript
* dependencies, if any. The default is {@code null}.
*/
public void setCss(ResourceReference css)
{
assertMutable();
this.css = css;
}
/**
* Iterate over all dependencies in the order they should appear in the
* <head>.
*/
public Iterator<ResourceReference> iterator()
{
return this.resources.iterator();
}
public ResourceReference getRootReference()
{
return isEmpty() ? null : this.resources.get(this.resources.size() - 1);
}
/**
* Returns {@code true} if this collection does not contain any script
* references.
*/
public boolean isEmpty()
{
return this.resources.size() == 0;
}
/**
* Make this object immutable so that any modifications will cause
* IllegalStateException to be thrown.
*/
public void freeze()
{
this.frozen = true;
}
/**
* Copy internal state to another instance. The frozen status will not
* be copied.
*/
public void copyTo(DependencyCollection other)
{
Args.notNull(other, "other");
other.position = this.position;
other.css = this.css;
other.resources = new ArrayList<ResourceReference>(this.resources);
}
private void assertMutable()
{
if(this.frozen)
{
throw new IllegalStateException("Frozen. Cannot be modified.");
}
}
}