/*
* FreeMarker: a tool that allows Java programs to generate HTML
* output using templates.
* Copyright (C) 1998-2004 Benjamin Geer
* Email: beroul@users.sourceforge.net
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package freemarker.template.expression;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import freemarker.template.FastHash;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateWriteableHashModel;
/**
* Represents a literal Hash model in a FM-Classic template. The hash model is
* not evaluated until run time, since the model may contain variables or other
* more complex expressions that can't be determined at compile time.
*
* @version $Id: HashLiteral.java 1123 2005-10-04 10:48:25Z run2000 $
*/
public final class HashLiteral implements Expression, Serializable {
private Map values;
/** Serialization UUID for this class. */
private static final long serialVersionUID = 7012055010641913273L;
/**
* Serialized form is two arrays, both the same size. One contains
* expressions to be evaluated as names, the other contains expressions to
* be evaluated as values. This is done primarily for type correctness, and
* avoids serializing a Map object.
*
* @serialField
* names Expression[] an array of name expressions
* @serialField
* values Expression[] an array of value expressions
* associated with the names
*/
private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("names", Expression[].class), new ObjectStreamField("values", Expression[].class) };
/**
* Constructor that takes a list of {@link Expression} elements to be
* evaluated as a hash model at run time.
*
* @param values
* the values to be added to the <code>HashLiteral</code>
* @throws NullPointerException
* the value list is null
* @throws IllegalArgumentException
* there are an odd number of arguments in the value list
*/
public HashLiteral(List values) {
Map cHash = new HashMap();
int iList;
if ((values.size() % 2) != 0) {
throw new IllegalArgumentException("Incorrect number of parameters supplied for a hash literal");
}
for (iList = 0; iList < values.size(); iList += 2) {
cHash.put(values.get(iList), values.get(iList + 1));
}
this.values = cHash;
}
/**
* The {@link freemarker.template.TemplateModel} value of this
* <code>Expression</code>.
*
* @param modelRoot
* the template model that will be evaluated by the expression
* @return a <code>FastHash</code> containing the values in the hash model
* @throws TemplateException
* the expression could not be evaluated for some reason
*/
public TemplateModel getAsTemplateModel(TemplateWriteableHashModel modelRoot) throws TemplateException {
Map hash = new HashMap();
Expression cKey, cValue;
Iterator iItem;
if (values != null) {
iItem = values.keySet().iterator();
while (iItem.hasNext()) {
cKey = (Expression) iItem.next();
cValue = (Expression) values.get(cKey);
hash.put(ExpressionUtils.getAsString(cKey.getAsTemplateModel(modelRoot)), cValue.getAsTemplateModel(modelRoot));
}
}
return new FastHash(hash);
}
/**
* Has the <code>HashLiteral</code> been populated?
*
* @return <code>true</code> if the <code>HashLiteral</code> is populated,
* otherwise <code>false</code>
*/
public boolean isComplete() {
return true;
}
/**
* Determine the type of result that can be calculated by this expression.
* This is in the form of an integer constant ored together from values in
* the {@link ExpressionUtils} class.
*/
public int getType() {
return ExpressionUtils.EXPRESSION_TYPE_HASH;
}
/**
* Determine whether result calculated by this expression is a constant
* value.
*/
public boolean isConstant() {
return false;
}
/**
* For serialization, write this object as two arrays of Expressions. The
* first array contains the expressions that will be evaluated as names of
* the hash. The seconds array contains the expressions that will be
* evaluated as values of the hash.
*
* @param stream
* the output stream to write this object to
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
ObjectOutputStream.PutField fields = stream.putFields();
int size = values.size();
Expression[] expressionNames = new Expression[size];
Expression[] expressionValues = new Expression[size];
Iterator iValues = values.keySet().iterator();
int i = 0;
while (iValues.hasNext()) {
Expression name = (Expression) iValues.next();
expressionNames[i] = name;
expressionValues[i] = (Expression) values.get(name);
i++;
}
fields.put("names", expressionNames);
fields.put("values", expressionValues);
stream.writeFields();
}
/**
* For serialization purposes, resolve a deserialized instance to an
* instance in the expression cache.
*/
private Object readResolve() throws ObjectStreamException {
return ExpressionCache.cacheExpression(this);
}
/**
* For serialization, read this object as two arrays of Expressions. The
* first array contains the expressions that will be evaluated as names of
* the hash. The seconds array contains the expressions that will be
* evaluated as values of the hash. Test whether the arrays are the same
* size, and whether they are null.
*
* @param stream
* the input stream to read serialized objects from
*/
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = stream.readFields();
Expression[] expressionNames = (Expression[]) fields.get("names", null);
Expression[] expressionValues = (Expression[]) fields.get("values", null);
if ((expressionNames == null) || (expressionValues == null)) {
throw new InvalidObjectException("Cannot create a HashLiteral with a null map");
}
if (expressionNames.length != expressionValues.length) {
throw new InvalidObjectException("Cannot create a HashLiteral with an unbalanced map");
}
values = new HashMap();
int size = expressionNames.length;
for (int i = 0; i < size; i++) {
values.put(expressionNames[i], expressionValues[i]);
}
}
/**
* Returns a string representation of the object.
*
* @return a <code>String</code> representation of this expression
*/
public String toString() {
return values.toString();
}
/**
* Determines whether this object is equal to the given object.
*
* @param o
* the object to be compared with
* @return <code>true</code> if the objects are equal, otherwise
* <code>false</code>
*/
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof HashLiteral)) {
return false;
}
final HashLiteral hashLiteral = (HashLiteral) o;
return values.equals(hashLiteral.values);
}
/**
* Returns the hash code for this operator.
*
* @return the hash code of this object
*/
public int hashCode() {
return values.hashCode() + 23;
}
/**
* Resolves the current expression, possibly into a different expression
* object. This is loosely equivalent to the serialization protocol's
* <code>readResolve</code> method. Situations where this may be used are:
* <ul>
* <li>Caching frequently-used expression objects</li>
* <li>Evaluating constant expressions, and returning a constant reference</li>
* </ul>
*/
public Expression resolveExpression() throws TemplateException {
Map hash = new HashMap();
Expression cKey, cValue;
Iterator iItem;
// See if we can intelligently determine whether all the elements
// in this hash are constant. If yes, return a constant expression.
if (values != null) {
iItem = values.keySet().iterator();
while (iItem.hasNext()) {
cKey = (Expression) iItem.next();
cValue = (Expression) values.get(cKey);
if (cKey.isConstant() && cValue.isConstant()) {
hash.put(ExpressionUtils.getAsString(cKey.getAsTemplateModel(ExpressionBuilder.emptyModel)), cValue.getAsTemplateModel(ExpressionBuilder.emptyModel));
} else {
hash = null;
break;
}
}
}
Expression expr = this;
if (hash != null) {
expr = new Constant(new FastHash(hash));
}
return ExpressionCache.cacheExpression(expr);
}
}