Java tutorial
/* * Copyright 2008 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 com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.template.soy.base.SoyBackendKind; import com.google.template.soy.base.internal.BaseUtils; import com.google.template.soy.data.internalutils.NodeContentKinds; import com.google.template.soy.types.SoyEnumType; import com.google.template.soy.types.SoyObjectType; import com.google.template.soy.types.SoyType; import com.google.template.soy.types.aggregate.ListType; import com.google.template.soy.types.aggregate.MapType; import com.google.template.soy.types.aggregate.RecordType; import com.google.template.soy.types.aggregate.UnionType; import com.google.template.soy.types.primitive.SanitizedType; import java.util.List; import java.util.Map; import java.util.SortedSet; /** * Shared utilities specific to the JS Src backend. * * <p> Important: Do not use outside of Soy code (treat as superpackage-private). * */ public class JsSrcUtils { private JsSrcUtils() { } /** * Builds a version of the given string that has literal Unicode Format characters (Unicode * category "Cf") changed to valid JavaScript Unicode escapes (i.e. &92;u####). If the provided * string doesn't have any Unicode Format characters, then the same string is returned. * * @param str The string to escape. * @return A version of the given string that has literal Unicode Format characters (Unicode * category "Cf") changed to valid JavaScript Unicode escapes (i.e. &92;u####). */ public static String escapeUnicodeFormatChars(String str) { int len = str.length(); // Do a quick check first, because most strings do not contain Unicode format characters. boolean hasFormatChar = false; for (int i = 0; i < len; i++) { if (Character.getType(str.charAt(i)) == Character.FORMAT) { hasFormatChar = true; break; } } if (!hasFormatChar) { return str; } // Now we actually need to build a new string. StringBuilder out = new StringBuilder(len * 4 / 3); int codePoint; for (int i = 0; i < len; i += Character.charCount(codePoint)) { codePoint = str.codePointAt(i); if (Character.getType(codePoint) == Character.FORMAT) { BaseUtils.appendHexEscape(out, codePoint); } else { out.appendCodePoint(codePoint); } } return out.toString(); } /** * Given a Soy type, return the corresponding jscompiler doc type expression. */ public static String getJsTypeExpr(SoyType type) { return getJsTypeExpr(type, false, true); } public static String getJsTypeExpr(SoyType type, boolean addParensIfNeeded, boolean addRequiredIfNeeded) { String nonNullablePrefix = addRequiredIfNeeded ? "!" : ""; switch (type.getKind()) { case ANY: return "*"; case UNKNOWN: // Add parens to avoid confusion w/ the leading ? of a nullable type return "(?)"; case NULL: return "null"; case BOOL: return "boolean"; case STRING: return "string"; case INT: case FLOAT: return "number"; case LIST: { ListType listType = (ListType) type; if (listType.getElementType().getKind() == SoyType.Kind.ANY) { return nonNullablePrefix + "Array"; } return nonNullablePrefix + "Array<" + getJsTypeExpr(listType.getElementType(), false, true) + ">"; } case MAP: { MapType mapType = (MapType) type; if (mapType.getKeyType().getKind() == SoyType.Kind.ANY && mapType.getValueType().getKind() == SoyType.Kind.ANY) { return nonNullablePrefix + "Object<?,?>"; } String keyTypeName = getJsTypeExpr(mapType.getKeyType(), false, true); String valueTypeName = getJsTypeExpr(mapType.getValueType(), false, true); return nonNullablePrefix + "Object<" + keyTypeName + "," + valueTypeName + ">"; } case RECORD: { RecordType recordType = (RecordType) type; if (recordType.getMembers().isEmpty()) { return "!Object"; } List<String> members = Lists.newArrayListWithExpectedSize(recordType.getMembers().size()); for (Map.Entry<String, SoyType> member : recordType.getMembers().entrySet()) { members.add(member.getKey() + ": " + getJsTypeExpr(member.getValue(), true, true)); } return "{" + Joiner.on(", ").join(members) + "}"; } case UNION: { UnionType unionType = (UnionType) type; SortedSet<String> typeNames = Sets.newTreeSet(); boolean isNullable = unionType.isNullable(); boolean hasNullableMember = false; for (SoyType memberType : unionType.getMembers()) { if (memberType.getKind() == SoyType.Kind.NULL) { continue; } if (memberType instanceof SanitizedType) { typeNames.add(getJsTypeName(memberType)); typeNames.add("string"); hasNullableMember = true; continue; } if (JsSrcUtils.isDefaultOptional(memberType)) { hasNullableMember = true; } String typeExpr = getJsTypeExpr(memberType, false, !isNullable); if (typeExpr.equals("?")) { throw new IllegalStateException("Type: " + unionType + " contains an unknown"); } typeNames.add(typeExpr); } if (isNullable && !hasNullableMember) { typeNames.add("null"); } if (isNullable) { typeNames.add("undefined"); } if (typeNames.size() != 1) { String result = Joiner.on("|").join(typeNames); if (addParensIfNeeded) { result = "(" + result + ")"; } return result; } else { return typeNames.first(); } } default: if (type instanceof SanitizedType) { String result = NodeContentKinds .toJsSanitizedContentCtorName(((SanitizedType) type).getContentKind()) + "|string"; if (addParensIfNeeded) { result = "(" + result + ")"; } return result; } return getJsTypeName(type); } } /** * Given a Soy type, return the corresponding jscompiler type name. Only * handles types which have names and have a declared constructor - not * arbitrary type expressions. */ public static String getJsTypeName(SoyType type) { if (type instanceof SanitizedType) { return NodeContentKinds.toJsSanitizedContentCtorName(((SanitizedType) type).getContentKind()); } else if (type.getKind() == SoyType.Kind.OBJECT) { return ((SoyObjectType) type).getNameForBackend(SoyBackendKind.JS_SRC); } else if (type.getKind() == SoyType.Kind.ENUM) { return ((SoyEnumType) type).getNameForBackend(SoyBackendKind.JS_SRC); } else { throw new AssertionError("Unsupported type: " + type); } } /** Returns true if the given type is optional by default (in the jscompiler). */ public static boolean isDefaultOptional(SoyType type) { switch (type.getKind()) { case OBJECT: case LIST: case MAP: return true; default: return type instanceof SanitizedType; } } /** * Returns true if key is a JavaScript reserved word. */ public static boolean isReservedWord(String key) { return JS_RESERVED_WORDS.contains(key); } /** * Set of words that JavaScript considers reserved words. These words cannot * be used as identifiers. This list is from the ECMA-262 v5, section 7.6.1: * http://www.ecma-international.org/publications/files/drafts/tc39-2009-050.pdf * plus the keywords for boolean values and {@code null}. * (Also includes the identifiers "soy" and "soydata" which are used internally by * Soy.) */ private static final ImmutableSet<String> JS_RESERVED_WORDS = ImmutableSet.of("break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "implements", "import", "in", "instanceof", "interface", "let", "null", "new", "package", "private", "protected", "public", "return", "soy", "soydata", "static", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield"); }