// Copyright (C) 2009 Google Inc.
//
// 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.google.caja.parser.js;
import com.google.caja.lexer.FilePosition;
import com.google.caja.lexer.TokenConsumer;
import com.google.caja.parser.AbstractParseTreeNode;
import com.google.caja.render.Concatenator;
import com.google.caja.render.JsPrettyPrinter;
import com.google.caja.util.Callback;
import java.io.IOException;
import java.util.List;
/**
* The result of running the cajoler over some content.
*
* @author ihab.awad@gmail.com
*/
public final class CajoledModule extends AbstractParseTreeNode {
private static final long serialVersionUID = -2499144011243193616L;
/** @param value unused. This ctor is provided for reflection. */
@ReflectiveCtor
public CajoledModule(FilePosition pos,
Void value,
List<? extends ObjectConstructor> children) {
this(pos, children.get(0));
assert children.size() == 1;
}
/**
* Creates a CajoledModule.
*
* @param pos a file position.
* @param body an object constructor representing the module.
*/
public CajoledModule(FilePosition pos, ObjectConstructor body) {
super(pos, ObjectConstructor.class);
createMutation().appendChild(body).execute();
}
/**
* Creates a CajoledModule.
*
* @param body an object constructor representing the module.
*/
public CajoledModule(ObjectConstructor body) {
this(body.getFilePosition(), body);
}
@Override
protected void childrenChanged() {
super.childrenChanged();
if (children().size() != 1 && getModuleBody() != null) {
throw new IllegalStateException(
"A CajoledModule may only have one child");
}
}
@Override
public Object getValue() { return null; }
@Override
public List<? extends ObjectConstructor> children() {
return childrenAs(ObjectConstructor.class);
}
public ObjectConstructor getModuleBody() {
return children().get(0);
}
/** The URI from which the module was loaded. */
public String getSrc() {
ValueProperty p = (ValueProperty) getModuleBody().propertyWithName("src");
if (p == null) { return null; }
return ((StringLiteral) p.getValueExpr()).getUnquotedValue();
}
/** The URIs of modules needed by this module. */
public ArrayConstructor getIncludedModules() {
ValueProperty p = (ValueProperty) getModuleBody().propertyWithName(
"includedModules");
return p != null ? (ArrayConstructor) p.getValueExpr() : null;
}
/** The URIs of modules inlined by this module. */
public ArrayConstructor getInlinedModules() {
ValueProperty p = (ValueProperty) getModuleBody().propertyWithName(
"inlinedModules");
return p != null ? (ArrayConstructor) p.getValueExpr() : null;
}
public FunctionConstructor getInstantiateMethod() {
return (FunctionConstructor) ((ValueProperty)
getModuleBody().propertyWithName("instantiate")).getValueExpr();
}
public final TokenConsumer makeRenderer(
Appendable out, Callback<IOException> exHandler) {
return new JsPrettyPrinter(new Concatenator(out, exHandler));
}
/**
* Returns a flattened copy of this CajoledModule. The flattened copy
* is cheaper to clone and/or cache.
* <p>
* Moderately-large JS (such as jquery.js) becomes a large ParseTreeNode
* structure that takes a nontrivial amount of time to clone or
* deserialize. However, once we've finished cajoling a module,
* the tree structure becomes irrelevant, so we can render the
* module body into a single string for efficiency.
* <p>
* This requires making an early decision on what renderer to use.
*/
public CajoledModule flatten(boolean minify) {
ObjectConstructor oc = new ObjectConstructor(FilePosition.UNKNOWN);
for (ObjProperty p : getModuleBody().children()) {
oc.appendChild(flattenProperty(p, minify));
}
return new CajoledModule(oc);
}
private ObjProperty flattenProperty(ObjProperty op, boolean minify) {
if (op instanceof ValueProperty) {
ValueProperty vp = (ValueProperty) op;
String name = vp.getPropertyName();
if ("instantiate".equals(name)) {
return new ValueProperty(
vp.getPropertyNameNode(),
new RenderedExpression(vp.getValueExpr(), minify));
}
}
return op;
}
}