/*
* Copyright 2012 Jason Miller
*
* 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 jj.resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import jj.application.AppLocation;
import jj.script.Global;
import jj.script.RhinoContext;
/**
* <p>
* Exposes information about the Resource contents of ResourceCache to
* the script API layer. Specifically, it mediates accessibility to the
* data structures, and performs transformation steps to collate interesting
* facts about the various resources.
*
* <p>
* This class is written with the assumption that it is being consumed by an
* client using d3's force layout. in particular, the list of links just exposes
* tiny objects that reference the indexes in the node list, to save the trouble
* of passing the whole world over the wire a few times, since the force layout
* allows that optimization
*
* <p>
* Basic lifecycle is
* <ol>
* <li>get an instance of this class. A single instance is a snapshot in time
* of the cache contents
* <li>call the appropriate methods to retrieve transformed information about
* individual resource nodes and their dependency relationships
* <li>discard
* </ol>
*
* <p>
* Why not just put all this in the ResourceCache, you might ask. I'll tell you!
* because that shouldn't be public, but the script API requires public classes,
* and because the transformation steps can be isolated, so they should be isolated.
* no need to give one class two jobs.
*
* @author jason
*
*/
public class ResourceCacheInspector {
private final List<Resource<?>> resources;
private final Provider<RhinoContext> contextProvider;
private final ScriptableObject global;
private final Map<Resource<?>, Integer> reverseIndex = new HashMap<>();
private final Scriptable nodes;
private final Scriptable links;
private final Scriptable types;
private final Scriptable bases;
@Inject
ResourceCacheInspector(
final ResourceCache resourceCache,
final ResourceCreators resourceCreators,
final Provider<RhinoContext> contextProvider,
final @Global ScriptableObject global
) {
this.resources = resourceCache.allResources();
this.contextProvider = contextProvider;
this.global = global;
nodes = makeNodes();
links = makeLinks();
types = makeTypes(resourceCreators.knownResourceTypeNames());
bases = makeBases();
}
private Scriptable makeNodes() {
try (RhinoContext context = contextProvider.get()) {
Scriptable resultArray = context.newArray(global, resources.size());
int index = 0;
for (Resource<?> resource : resources) {
reverseIndex.put(resource, index);
Scriptable rObj = context.newObject(global);
((AbstractResource<?>)resource).describe(rObj);
resultArray.put(index++, resultArray, rObj);
}
return resultArray;
}
}
private Scriptable makeLinks() {
try (RhinoContext context = contextProvider.get()) {
// maybe no links! maybe a freakin million! WE DON'T KNOW!
Scriptable resultArray = context.newArray(global, 0);
int index = 0;
for (Resource<?> r1 : resources) {
for (AbstractResource<?> r2 : ((AbstractResource<?>)r1).dependents()) {
Scriptable link = context.newObject(global);
link.put("source", link, reverseIndex.get(r1));
link.put("target", link, reverseIndex.get(r2));
resultArray.put(index++, resultArray, link);
}
}
return resultArray;
}
}
private Scriptable makeTypes(List<String> typeNames) {
try (RhinoContext context = contextProvider.get()) {
Scriptable resultArray = context.newArray(global, typeNames.size());
int index = 0;
for (String typeName : typeNames) {
resultArray.put(index++, resultArray, typeName);
}
return resultArray;
}
}
private Scriptable makeBases() {
try (RhinoContext context = contextProvider.get()) {
Scriptable resultArray = context.newArray(global, AppLocation.values().length);
int index = 0;
for (AppLocation baseName : AppLocation.values()) {
resultArray.put(index++, resultArray, baseName.name());
}
return resultArray;
}
}
public Scriptable nodes() {
return nodes;
}
public Scriptable links() {
return links;
}
public Scriptable types() {
return types;
}
public Scriptable bases() {
return bases;
}
}