Java tutorial
/* * Copyright 2018 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.template.soy.jssrc.internal; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CaseFormat; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.base.internal.BaseUtils; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.SoyErrorKind; import com.google.template.soy.error.SoyErrorKind.StyleAllowance; import com.google.template.soy.internal.i18n.BidiGlobalDir; import com.google.template.soy.jssrc.SoyJsSrcOptions; import com.google.template.soy.jssrc.dsl.CodeChunk; import com.google.template.soy.jssrc.dsl.CodeChunkUtils; import com.google.template.soy.jssrc.dsl.Expression; import com.google.template.soy.jssrc.dsl.GoogRequire; import com.google.template.soy.jssrc.restricted.JsExpr; import com.google.template.soy.plugin.javascript.restricted.JavaScriptPluginContext; import com.google.template.soy.plugin.javascript.restricted.JavaScriptValue; import com.google.template.soy.plugin.javascript.restricted.JavaScriptValueFactory; import com.google.template.soy.plugin.javascript.restricted.SoyJavaScriptSourceFunction; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Implementation of {@link JavaScriptValueFactory} that delegates to the {@link Expression} API. */ public final class JavaScriptValueFactoryImpl extends JavaScriptValueFactory { private static final JavaScriptValueImpl ERROR_VALUE = new JavaScriptValueImpl(Expression.fromExpr(new JsExpr( "(function(){throw new Error('if you see this, the soy compiler has swallowed " + "an error :-(');})()", Integer.MAX_VALUE), ImmutableList.<GoogRequire>of())); private static final SoyErrorKind NULL_RETURN = SoyErrorKind .of(formatPlain("{2}.applyForJavaScriptSource returned null."), StyleAllowance.NO_PUNCTUATION); private static final SoyErrorKind UNEXPECTED_ERROR = SoyErrorKind.of(formatPlain("{2}"), StyleAllowance.NO_PUNCTUATION); private static String formatPlain(String innerFmt) { return "Error in plugin implementation for function ''{0}''." + "\n" + innerFmt + "\nPlugin implementation: {1}"; } private final ErrorReporter reporter; private final SoyJsSrcOptions jsSrcOptions; private final BidiGlobalDir dir; private JavaScriptPluginContext createContext(final CodeChunk.Generator codeGenerator) { return new JavaScriptPluginContext() { @Override public JavaScriptValue getBidiDir() { if (dir.isStaticValue()) { return new JavaScriptValueImpl(Expression.number(dir.getStaticValue())); } return new JavaScriptValueImpl( Expression.ifExpression(JsRuntime.SOY_IS_LOCALE_RTL, Expression.number(-1)) .setElse(Expression.number(1)).build(codeGenerator)); } }; } public JavaScriptValueFactoryImpl(SoyJsSrcOptions jsSrcOptions, BidiGlobalDir dir, ErrorReporter reporter) { this.dir = dir; this.jsSrcOptions = jsSrcOptions; this.reporter = reporter; } Expression applyFunction(SourceLocation location, String name, SoyJavaScriptSourceFunction fn, List<Expression> args, CodeChunk.Generator codeGenerator) { JavaScriptValueImpl result; try { result = (JavaScriptValueImpl) fn.applyForJavaScriptSource(this, wrapParams(args), createContext(codeGenerator)); if (result == null) { report(location, name, fn, NULL_RETURN, fn.getClass().getSimpleName()); result = ERROR_VALUE; } } catch (Throwable t) { BaseUtils.trimStackTraceTo(t, getClass()); report(location, name, fn, UNEXPECTED_ERROR, Throwables.getStackTraceAsString(t)); result = ERROR_VALUE; } return result.impl; } private void report(SourceLocation location, String name, SoyJavaScriptSourceFunction fn, SoyErrorKind error, Object... additionalArgs) { Object[] args = new Object[additionalArgs.length + 2]; args[0] = name; args[1] = fn.getClass().getName(); System.arraycopy(additionalArgs, 0, args, 2, additionalArgs.length); reporter.report(location, error, args); } private Expression chainedDotAccess(Expression base, String suffix) { for (String part : Splitter.on('.').splitToList(suffix)) { base = base.dotAccess(part); } return base; } private Expression referenceModuleExport(String moduleName, String export) { Expression module; if (jsSrcOptions.shouldGenerateGoogModules()) { String alias = "$" + CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, moduleName.replace('.', '_')); module = GoogRequire.createWithAlias(moduleName, alias).reference(); } else { module = GoogRequire.create(moduleName).googModuleGet(); } return chainedDotAccess(module, export); } @Override public JavaScriptValue getModuleExport(String moduleName, String export) { return new JavaScriptValueImpl(referenceModuleExport(moduleName, export)); } @Override public JavaScriptValue callModuleFunction(String moduleName, String functionName, JavaScriptValue... params) { return new JavaScriptValueImpl( referenceModuleExport(moduleName, functionName).call(unwrapParams(Arrays.asList(params)))); } @Override public JavaScriptValue callNamespaceFunction(String googProvide, String fullFunctionName, JavaScriptValue... params) { checkArgument( fullFunctionName.startsWith(googProvide) && (fullFunctionName.length() == googProvide.length() || fullFunctionName.charAt(googProvide.length()) == '.'), "expected '%s' to be in the namespace of '%s'. '%s' should be fully qualified", fullFunctionName, googProvide, fullFunctionName); GoogRequire require = GoogRequire.create(googProvide); Expression function; if (fullFunctionName.length() == googProvide.length()) { function = require.reference(); } else { function = chainedDotAccess(require.reference(), fullFunctionName.substring(googProvide.length() + 1)); } return new JavaScriptValueImpl(function.call(unwrapParams(Arrays.asList(params)))); } @Override public JavaScriptValueImpl unsafeUncheckedExpression(String expr) { return new JavaScriptValueImpl( Expression.fromExpr(new JsExpr(expr, /*precedence=*/ 0), ImmutableList.<GoogRequire>of())); } @Override public JavaScriptValueImpl constant(long num) { return new JavaScriptValueImpl(Expression.number(num)); } @Override public JavaScriptValueImpl constant(double num) { return new JavaScriptValueImpl(Expression.number(num)); } @Override public JavaScriptValueImpl constant(String str) { return new JavaScriptValueImpl(Expression.stringLiteral(str)); } @Override public JavaScriptValueImpl constant(boolean bool) { return new JavaScriptValueImpl(bool ? Expression.LITERAL_TRUE : Expression.LITERAL_FALSE); } @Override public JavaScriptValue constantNull() { return new JavaScriptValueImpl(Expression.LITERAL_NULL); } @Override public JavaScriptValueImpl global(String globalSymbol) { return new JavaScriptValueImpl(Expression.dottedIdNoRequire(globalSymbol)); } private static List<Expression> unwrapParams(List<JavaScriptValue> params) { List<Expression> exprs = new ArrayList<>(params.size()); for (JavaScriptValue v : params) { exprs.add(((JavaScriptValueImpl) v).impl); } return exprs; } private static List<JavaScriptValue> wrapParams(List<Expression> params) { List<JavaScriptValue> exprs = new ArrayList<>(params.size()); for (Expression e : params) { exprs.add(new JavaScriptValueImpl(e)); } return exprs; } @VisibleForTesting static final class JavaScriptValueImpl implements JavaScriptValue { @VisibleForTesting final Expression impl; JavaScriptValueImpl(Expression impl) { this.impl = checkNotNull(impl); } @Override public JavaScriptValueImpl isNonNull() { return new JavaScriptValueImpl(impl.doubleNotEquals(Expression.LITERAL_NULL)); } @Override public JavaScriptValueImpl isNull() { return new JavaScriptValueImpl(impl.doubleEquals(Expression.LITERAL_NULL)); } @Override public Optional<String> asStringLiteral() { return impl.asStringLiteral(); } @Override public JavaScriptValueImpl coerceToString() { return new JavaScriptValueImpl(CodeChunkUtils.concatChunksForceString(ImmutableList.of(impl))); } @Override public JavaScriptValueImpl invokeMethod(String methodName, JavaScriptValue... args) { return new JavaScriptValueImpl(impl.dotAccess(methodName).call(unwrapParams(Arrays.asList(args)))); } @Override public JavaScriptValueImpl accessProperty(String ident) { return new JavaScriptValueImpl(impl.dotAccess(ident)); } @Override public String toString() { return impl.getCode(); } } }