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; import static com.google.common.base.Strings.emptyToNull; import com.google.common.base.Converter; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.base.internal.SanitizedContentKind; import com.google.template.soy.base.internal.SoyFileKind; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.SoyErrorKind; import com.google.template.soy.soytree.CompilationUnit; import com.google.template.soy.soytree.DataAllCallSituationP; import com.google.template.soy.soytree.ParameterP; import com.google.template.soy.soytree.SanitizedContentKindP; import com.google.template.soy.soytree.SoyFileNode; import com.google.template.soy.soytree.SoyFileP; import com.google.template.soy.soytree.SoyFileSetNode; import com.google.template.soy.soytree.SoyTypeP; import com.google.template.soy.soytree.TemplateDelegateNodeBuilder; import com.google.template.soy.soytree.TemplateKindP; import com.google.template.soy.soytree.TemplateMetadata; import com.google.template.soy.soytree.TemplateMetadata.DataAllCallSituation; import com.google.template.soy.soytree.TemplateMetadata.Kind; import com.google.template.soy.soytree.TemplateMetadata.Parameter; import com.google.template.soy.soytree.TemplateMetadataP; import com.google.template.soy.soytree.TemplateNode; import com.google.template.soy.soytree.TemplateRegistry; import com.google.template.soy.soytree.Visibility; import com.google.template.soy.soytree.VisibilityP; import com.google.template.soy.types.AnyType; import com.google.template.soy.types.BoolType; import com.google.template.soy.types.ErrorType; import com.google.template.soy.types.FloatType; import com.google.template.soy.types.IntType; import com.google.template.soy.types.NullType; import com.google.template.soy.types.SanitizedType.AttributesType; import com.google.template.soy.types.SanitizedType.HtmlType; import com.google.template.soy.types.SanitizedType.JsType; import com.google.template.soy.types.SanitizedType.StyleType; import com.google.template.soy.types.SanitizedType.TrustedResourceUriType; import com.google.template.soy.types.SanitizedType.UriType; import com.google.template.soy.types.SoyProtoEnumType; import com.google.template.soy.types.SoyProtoType; import com.google.template.soy.types.SoyType; import com.google.template.soy.types.SoyTypeRegistry; import com.google.template.soy.types.StringType; import com.google.template.soy.types.UnknownType; import com.google.template.soy.types.VeDataType; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; /** Utilities to transform TemplateMetadata objects to and From CompilationUnit protos */ public final class TemplateMetadataSerializer { private static final SoyErrorKind UNABLE_TO_PARSE_TEMPLATE_HEADER = SoyErrorKind .of("Unable to parse template header for {0} from Soy file {1}: {2}."); private static final SoyErrorKind UNABLE_TO_FIND_TYPE = SoyErrorKind .of("Unable to {0}: {1} referenced by dependency."); private static final SoyErrorKind UNEXPECTED_TYPE = SoyErrorKind .of("Expected {0} to be a {1} but it was a {2}."); private static final Converter<VisibilityP, Visibility> VISIBILITY_CONVERTER = createEnumConverter( VisibilityP.class, Visibility.class); private static final Converter<TemplateKindP, TemplateMetadata.Kind> TEMPLATE_KIND_CONVERTER = createEnumConverter( TemplateKindP.class, TemplateMetadata.Kind.class); private static final Converter<SanitizedContentKindP, SanitizedContentKind> CONTENT_KIND_CONVERTER = createEnumConverter( SanitizedContentKindP.class, SanitizedContentKind.class); private TemplateMetadataSerializer() { } public static CompilationUnit compilationUnitFromFileSet(SoyFileSetNode fileSet, TemplateRegistry registry) { CompilationUnit.Builder builder = CompilationUnit.newBuilder(); for (SoyFileNode file : fileSet.getChildren()) { SoyFileP.Builder fileBuilder = SoyFileP.newBuilder().setNamespace(file.getNamespace()) .setDelpackage(Strings.nullToEmpty(file.getDelPackageName())).setFilePath(file.getFilePath()); for (TemplateNode template : file.getChildren()) { TemplateMetadata meta = registry.getMetadata(template); fileBuilder.addTemplate(protoFromTemplate(meta, file)); } builder.addFile(fileBuilder.build()); } return builder.build(); } public static ImmutableList<TemplateMetadata> templatesFromCompilationUnit(CompilationUnit compilationUnit, SoyFileKind fileKind, SoyTypeRegistry typeRegistry, String filePath, ErrorReporter errorReporter) { ImmutableList.Builder<TemplateMetadata> templates = ImmutableList.builder(); for (SoyFileP fileProto : compilationUnit.getFileList()) { for (TemplateMetadataP templateProto : fileProto.getTemplateList()) { try { templates.add(metadataFromProto(fileProto, templateProto, fileKind, typeRegistry, filePath, errorReporter)); } catch (IllegalArgumentException iae) { errorReporter.report(new SourceLocation(filePath), UNABLE_TO_PARSE_TEMPLATE_HEADER, templateProto.getTemplateName(), fileProto.getFilePath(), iae.getMessage()); } } } return templates.build(); } private static TemplateMetadataP protoFromTemplate(TemplateMetadata meta, SoyFileNode fileNode) { return TemplateMetadataP.newBuilder() .setTemplateName(meta.getTemplateKind() == Kind.DELTEMPLATE ? meta.getDelTemplateName() : maybeShortenTemplateName(fileNode.getNamespace(), meta.getTemplateName())) .setTemplateKind(TEMPLATE_KIND_CONVERTER.reverse().convert(meta.getTemplateKind())) .setVisibility(VISIBILITY_CONVERTER.reverse().convert(meta.getVisibility())) .setContentKind(meta.getContentKind() == null ? SanitizedContentKindP.NONE : CONTENT_KIND_CONVERTER.reverse().convert(meta.getContentKind())) .setDelTemplateVariant(Strings.nullToEmpty(meta.getDelTemplateVariant())) .setStrictHtml(meta.isStrictHtml()) .addAllDataAllCallSituation(protosFromCallSitatuations(meta.getDataAllCallSituations(), fileNode)) .addAllParameter(protosFromParameters(meta.getParameters())).build(); } private static TemplateMetadata metadataFromProto(SoyFileP fileProto, TemplateMetadataP templateProto, SoyFileKind fileKind, SoyTypeRegistry typeRegistry, String filePath, ErrorReporter errorReporter) { TemplateMetadata.Builder builder = TemplateMetadata.builder(); TemplateMetadata.Kind templateKind = TEMPLATE_KIND_CONVERTER.convert(templateProto.getTemplateKind()); @Nullable String delPackageName = emptyToNull(fileProto.getDelpackage()); String templateName; switch (templateKind) { case ELEMENT: case BASIC: templateName = fileProto.getNamespace() + templateProto.getTemplateName(); break; case DELTEMPLATE: String variant = templateProto.getDelTemplateVariant(); String delTemplateName = templateProto.getTemplateName(); templateName = fileProto.getNamespace() + TemplateDelegateNodeBuilder .partialDeltemplateTemplateName(delTemplateName, delPackageName, variant); builder.setDelTemplateVariant(variant).setDelTemplateName(delTemplateName); break; default: throw new AssertionError(); } return builder.setTemplateName(templateName).setSoyFileKind(fileKind).setDelPackageName(delPackageName) .setStrictHtml(templateProto.getStrictHtml()).setTemplateKind(templateKind) .setSourceLocation(new SourceLocation(fileProto.getFilePath())) .setContentKind(templateProto.getContentKind() == SanitizedContentKindP.NONE ? null : CONTENT_KIND_CONVERTER.convert(templateProto.getContentKind())) .setVisibility(VISIBILITY_CONVERTER.convert(templateProto.getVisibility())) .setDataAllCallSituations( callSituationsFromProto(templateProto.getDataAllCallSituationList(), fileProto)) .setParameters(parametersFromProto(templateProto.getParameterList(), typeRegistry, filePath, errorReporter)) .build(); } private static ImmutableList<Parameter> parametersFromProto(List<ParameterP> parameterList, SoyTypeRegistry typeRegistry, String filePath, ErrorReporter errorReporter) { ImmutableList.Builder<Parameter> builder = ImmutableList.builderWithExpectedSize(parameterList.size()); for (ParameterP parameter : parameterList) { builder.add(Parameter.builder().setName(parameter.getName()).setRequired(parameter.getRequired()) .setTypeLazily(new SoyTypeSupplier(parameter.getType(), typeRegistry, filePath, errorReporter)) .build()); } return builder.build(); } /** * Lazily parses a type. * * <p>We do this lazily because for many compiles we never access these types. For many * dependencies there are templates that are never called by the source templates, so there is no * point in fully resolving its types. */ private static final class SoyTypeSupplier implements Supplier<SoyType> { final SoyTypeP typeProto; final SoyTypeRegistry typeRegistry; final String filePath; final ErrorReporter errorReporter; SoyTypeSupplier(SoyTypeP type, SoyTypeRegistry typeRegistry, String filePath, ErrorReporter errorReporter) { this.typeProto = type; this.typeRegistry = typeRegistry; this.filePath = filePath; this.errorReporter = errorReporter; } @Override public SoyType get() { return fromProto(typeProto, typeRegistry, filePath, errorReporter); } } private static SoyType fromProto(SoyTypeP proto, SoyTypeRegistry typeRegistry, String filePath, ErrorReporter errorReporter) { switch (proto.getTypeKindCase()) { case PRIMITIVE: switch (proto.getPrimitive()) { case ANY: return AnyType.getInstance(); case UNKNOWN: return UnknownType.getInstance(); case INT: return IntType.getInstance(); case NULL: return NullType.getInstance(); case BOOL: return BoolType.getInstance(); case FLOAT: return FloatType.getInstance(); case STRING: return StringType.getInstance(); case HTML: return HtmlType.getInstance(); case ATTRIBUTES: return AttributesType.getInstance(); case JS: return JsType.getInstance(); case CSS: return StyleType.getInstance(); case URI: return UriType.getInstance(); case TRUSTED_RESOURCE_URI: return TrustedResourceUriType.getInstance(); case VE_DATA: return VeDataType.getInstance(); case UNRECOGNIZED: case UNKNOWN_PRIMITIVE_TYPE: // fall-through } throw new AssertionError("Unknown primitive: " + proto.getPrimitive()); case LIST_ELEMENT: return typeRegistry .getOrCreateListType(fromProto(proto.getListElement(), typeRegistry, filePath, errorReporter)); case LEGACY_OBJECT_MAP: return typeRegistry.getOrCreateLegacyObjectMapType( fromProto(proto.getLegacyObjectMap().getKey(), typeRegistry, filePath, errorReporter), fromProto(proto.getLegacyObjectMap().getValue(), typeRegistry, filePath, errorReporter)); case MAP: return typeRegistry.getOrCreateMapType( fromProto(proto.getMap().getKey(), typeRegistry, filePath, errorReporter), fromProto(proto.getMap().getValue(), typeRegistry, filePath, errorReporter)); case PROTO: { SoyType type = typeRegistry.getType(proto.getProto()); if (type == null) { errorReporter.report(new SourceLocation(filePath), UNABLE_TO_FIND_TYPE, "proto", proto.getProto()); return ErrorType.getInstance(); } // allow unknown to support message extraction which configures the DEFAULT_UNKNOWN type // registry if (type instanceof SoyProtoType || type == UnknownType.getInstance()) { return type; } errorReporter.report(new SourceLocation(filePath), UNEXPECTED_TYPE, proto.getProto(), "proto", type.getKind()); return ErrorType.getInstance(); } case PROTO_ENUM: { SoyType type = typeRegistry.getType(proto.getProtoEnum()); if (type == null) { errorReporter.report(new SourceLocation(filePath), UNABLE_TO_FIND_TYPE, "proto enum", proto.getProtoEnum()); return ErrorType.getInstance(); } // allow unknown to support message extraction which configures the DEFAULT_UNKNOWN type // registry if (type instanceof SoyProtoEnumType || type == UnknownType.getInstance()) { return type; } errorReporter.report(new SourceLocation(filePath), UNEXPECTED_TYPE, proto.getProtoEnum(), "proto enum", type.getKind()); return ErrorType.getInstance(); } case RECORD: { ImmutableSortedMap.Builder<String, SoyType> members = ImmutableSortedMap.naturalOrder(); for (Map.Entry<String, SoyTypeP> entry : proto.getRecord().getFieldMap().entrySet()) { members.put(entry.getKey(), fromProto(entry.getValue(), typeRegistry, filePath, errorReporter)); } return typeRegistry.getOrCreateRecordType(members.build()); } case UNION: { List<SoyType> members = new ArrayList<>(proto.getUnion().getMemberCount()); for (SoyTypeP member : proto.getUnion().getMemberList()) { members.add(fromProto(member, typeRegistry, filePath, errorReporter)); } return typeRegistry.getOrCreateUnionType(members); } case VE: return typeRegistry.getOrCreateVeType(proto.getVe()); case TYPEKIND_NOT_SET: // fall-through } throw new AssertionError("unhandled typeKind: " + proto.getTypeKindCase()); } private static ImmutableList<ParameterP> protosFromParameters(List<Parameter> parameterList) { ImmutableList.Builder<ParameterP> builder = ImmutableList.builderWithExpectedSize(parameterList.size()); for (Parameter parameter : parameterList) { builder.add(ParameterP.newBuilder().setName(parameter.getName()).setType(parameter.getType().toProto()) .setRequired(parameter.isRequired()).build()); } return builder.build(); } private static ImmutableList<DataAllCallSituation> callSituationsFromProto( List<DataAllCallSituationP> callSituationList, SoyFileP fileProto) { ImmutableList.Builder<DataAllCallSituation> builder = ImmutableList .builderWithExpectedSize(callSituationList.size()); for (DataAllCallSituationP call : callSituationList) { String templateName = call.getTemplateName(); if (templateName.startsWith(".")) { templateName = fileProto.getNamespace() + templateName; } builder.add(DataAllCallSituation.builder().setTemplateName(templateName).setDelCall(call.getDelCall()) .setExplicitlyPassedParameters(ImmutableSet.copyOf(call.getExplicitlyPassedParametersList())) .build()); } return builder.build(); } private static ImmutableList<DataAllCallSituationP> protosFromCallSitatuations( ImmutableList<DataAllCallSituation> callSituationList, SoyFileNode fileNode) { ImmutableList.Builder<DataAllCallSituationP> builder = ImmutableList .builderWithExpectedSize(callSituationList.size()); for (DataAllCallSituation call : callSituationList) { builder.add(DataAllCallSituationP.newBuilder() .setTemplateName(call.isDelCall() ? call.getTemplateName() : maybeShortenTemplateName(fileNode.getNamespace(), call.getTemplateName())) .setDelCall(call.isDelCall()) .addAllExplicitlyPassedParameters(call.getExplicitlyPassedParameters()).build()); } return builder.build(); } private static String maybeShortenTemplateName(String namespace, String templateName) { if (templateName.startsWith(namespace) && templateName.indexOf('.', namespace.length() + 2) == -1) { return templateName.substring(namespace.length()); } return templateName; } private static <T1 extends Enum<T1>, T2 extends Enum<T2>> Converter<T1, T2> createEnumConverter( final Class<T1> t1, final Class<T2> t2) { Map<String, T1> t1NameMap = new HashMap<>(); for (T1 instance : t1.getEnumConstants()) { t1NameMap.put(instance.name(), instance); } final EnumMap<T1, T2> forwardMap = new EnumMap<>(t1); final EnumMap<T2, T1> backwardMap = new EnumMap<>(t2); for (T2 t2Instance : t2.getEnumConstants()) { T1 t1Instance = t1NameMap.remove(t2Instance.name()); if (t1Instance != null) { forwardMap.put(t1Instance, t2Instance); backwardMap.put(t2Instance, t1Instance); } } return new Converter<T1, T2>() { @Override protected T2 doForward(T1 a) { T2 r = forwardMap.get(a); if (r == null) { throw new IllegalArgumentException("Failed to map: " + a + " to an instance of " + t2); } return r; } @Override protected T1 doBackward(T2 b) { T1 r = backwardMap.get(b); if (r == null) { throw new IllegalArgumentException("Failed to map: " + b + " to an instance of " + t1); } return r; } }; } }