/*
* Copyright (C) 2011 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.codegen.literal;
import static org.jboss.errai.codegen.builder.callstack.LoadClassReference.getClassReference;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.errai.codegen.AnnotationEncoder;
import org.jboss.errai.codegen.Context;
import org.jboss.errai.codegen.RenderCacheStore;
import org.jboss.errai.codegen.SnapshotMaker;
import org.jboss.errai.codegen.exception.NotLiteralizableException;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
/**
* The literal factory provides a LiteralValue for the specified object (if possible).
*
* @author Mike Brock <cbrock@redhat.com>
*/
public class LiteralFactory {
/**
* Returns a literal value (specialization of Statement) representing the
* given object in the given context.
*
* @param o
* The object to create a literal value for.
*
* @return a LiteralValue for the given object. Never null.
*
* @throws NotLiteralizableException
* if {@code o} cannot be literalized
*/
public static LiteralValue<?> getLiteral(final Object o) {
return getLiteral(null, o);
}
/**
* Returns a literal value (specialization of Statement) representing the
* given object in the given context.
*
* @param context
* The context the literal value will be code-generated in. Contexts
* can specify additional literalizable types. See {@link Context#addLiteralizableClass(Class)}.
* @param o
* The object to create a literal value for.
*
* @return a LiteralValue for the given object. Never null.
*
* @throws NotLiteralizableException
* if {@code o} cannot be literalized
*/
public static LiteralValue<?> getLiteral(final Context context, final Object o) {
return getLiteral(context, o, true);
}
private static final RenderCacheStore<Object, LiteralValue<?>> CLASS_LITERAL_RENDER_CACHE =
() -> "LITERAL_CACHE_STORE";
/**
* Implementation for the public getLiteral() methods.
*
* @param context
* The context the literal value will be code-generated in. Contexts
* can specify additional literalizable types.
* @param o
* The object to create a literal value for.
*
* @return a LiteralValue for the given object. Never null.
*
* @throws NotLiteralizableException
* if {@code o} cannot be literalized
*/
private static LiteralValue<?> getLiteral(final Context context,
final Object o,
final boolean throwIfNotLiteralizable) {
Map<Object, LiteralValue<?>> LITERAL_CACHE = null;
if (context != null) {
LITERAL_CACHE = context.getRenderingCache(CLASS_LITERAL_RENDER_CACHE);
}
LiteralValue<?> result = LITERAL_CACHE != null ? LITERAL_CACHE.get(o) : null;
if (result == null) {
if (o instanceof MetaClass) {
result = new MetaClassLiteral((MetaClass) o);
}
else if (o instanceof Annotation) {
result = new LiteralValue<Annotation>((Annotation) o) {
@Override
public String getCanonicalString(final Context context) {
return AnnotationEncoder.encode((Annotation) o).generate(context);
}
};
}
else if (o instanceof Enum) {
result = new LiteralValue<Enum>((Enum) o) {
@Override
public String getCanonicalString(final Context context) {
return getClassReference(MetaClassFactory.get(o.getClass()), context) + "." + ((Enum) o).name();
}
};
}
else {
result = _getLiteral(context, o, throwIfNotLiteralizable);
}
// avoid caching the null; we don't want that returned from the cache!
if (result != null && LITERAL_CACHE != null) {
LITERAL_CACHE.put(o, result);
}
}
return result;
}
private static LiteralValue<?> _getLiteral(final Context context,
final Object o,
final boolean throwIfNotLiteralizable) {
if (o == null) {
return NullLiteral.INSTANCE;
}
if (o instanceof String) {
return new StringLiteral((String) o);
}
else if (o instanceof Integer) {
return new IntValue((Integer) o);
}
else if (o instanceof Character) {
return new CharValue((Character) o);
}
else if (o instanceof Boolean) {
return new BooleanValue((Boolean) o);
}
else if (o instanceof Short) {
return new ShortValue((Short) o);
}
else if (o instanceof Long) {
return new LongValue((Long) o);
}
else if (o instanceof Double) {
return new DoubleValue((Double) o);
}
else if (o instanceof Float) {
return new FloatValue((Float) o);
}
else if (o instanceof Byte) {
return new ByteValue((Byte) o);
}
else if (o instanceof Class) {
return new ClassLiteral((Class) o);
}
else if (o instanceof MetaClass) {
return new MetaClassLiteral((MetaClass) o);
}
else if (o instanceof Set) {
return new SetValue((Set) o);
}
else if (o instanceof List) {
return new ListValue((List) o);
}
else if (o instanceof Map) {
return new MapValue((Map) o);
}
else if (o.getClass().isArray()) {
return new ArrayLiteral(o);
}
else if (context != null && context.isLiteralizableClass(o.getClass())) {
// the new instance of LiteralValue here provides surprising (but desirable) caching behaviour.
// see LiteralTest.testGenerateObjectArrayThenModifyThenGenerateAgain for details.
return new LiteralValue<Object>(o) {
@Override
public String getCanonicalString(final Context context) {
final Class<?> targetType = context.getLiteralizableTargetType(o.getClass());
return SnapshotMaker.makeSnapshotAsSubclass(o, targetType, targetType, null).generate(context);
}
};
}
else {
if (throwIfNotLiteralizable) {
throw new NotLiteralizableException(o);
}
return null;
}
}
/**
* Returns a literal value (specialization of Statement) representing the
* given object in the given context, or null if the value is not
* literalizable.
*
* @param o
* The object to create a literal value for.
*
* @return a LiteralValue for the given object, or null if the value cannot be
* expressed as a literal.
*/
public static LiteralValue<?> isLiteral(final Object o) {
return getLiteral(null, o, false);
}
}