diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core/lombok/ConfigurationKeys.java | 2 | ||||
-rw-r--r-- | src/core/lombok/core/PostCompiler.java | 10 | ||||
-rw-r--r-- | src/core/lombok/core/handlers/HandlerUtil.java | 19 | ||||
-rw-r--r-- | src/core/lombok/eclipse/EclipseAST.java | 2 | ||||
-rw-r--r-- | src/core/lombok/eclipse/TransformEclipseAST.java | 36 | ||||
-rw-r--r-- | src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java | 8 | ||||
-rw-r--r-- | src/core/lombok/javac/handlers/JavacHandlerUtil.java | 55 | ||||
-rw-r--r-- | src/delombok/lombok/delombok/PrettyPrinter.java | 26 | ||||
-rw-r--r-- | src/eclipseAgent/lombok/eclipse/agent/PatchVal.java | 25 | ||||
-rw-r--r-- | src/stubs/com/sun/tools/javac/code/Symbol.java | 8 | ||||
-rw-r--r-- | src/stubs/com/sun/tools/javac/code/Symtab.java | 2 | ||||
-rw-r--r-- | src/support/lombok/website/RunSite.java | 103 |
12 files changed, 247 insertions, 49 deletions
diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 60b98a94..01a4b576 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -140,7 +140,7 @@ public class ConfigurationKeys { * NB: GWT projects, and probably android projects, should explicitly set this key to {@code true} for the entire project. * * <br> - * <em>BREAKING CHANGE</em>: Starting with lombok v1.16.20, defaults to {@code false} instead of {@code true}, as {@code @ConstructorProperties} requires extra modules in JDK9. + * <em>BREAKING CHANGE</em>: Starting with lombok v1.16.20, defaults to {@code true} instead of {@code false}, as {@code @ConstructorProperties} requires extra modules in JDK9. * * @see ConfigurationKeys#ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES * @deprecated Since version 2.0, use {@link #ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES} instead. diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java index e17f806e..72f4b3a2 100644 --- a/src/core/lombok/core/PostCompiler.java +++ b/src/core/lombok/core/PostCompiler.java @@ -72,10 +72,12 @@ public final class PostCompiler { // no need to call super byte[] original = toByteArray(); byte[] copy = null; - try { - copy = applyTransformations(original, fileName, diagnostics); - } catch (Exception e) { - diagnostics.addWarning(String.format("Error during the transformation of '%s'; no post-compilation has been applied", fileName)); + if (original.length > 0) { + try { + copy = applyTransformations(original, fileName, diagnostics); + } catch (Exception e) { + diagnostics.addWarning(String.format("Error during the transformation of '%s'; no post-compilation has been applied", fileName)); + } } if (copy == null) { diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index c4b60475..92a9a142 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -759,6 +759,7 @@ public class HandlerUtil { } private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITH(?:ER)?)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + private static final Pattern LINE_BREAK_FINDER = Pattern.compile("(\\r?\\n)?"); public static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); @@ -767,27 +768,29 @@ public class HandlerUtil { } public static String stripSectionsFromJavadoc(String javadoc) { - Matcher m = SECTION_FINDER.matcher(javadoc); - if (!m.find()) return javadoc; + Matcher sectionMatcher = SECTION_FINDER.matcher(javadoc); + if (!sectionMatcher.find()) return javadoc; - return javadoc.substring(0, m.start()); + return javadoc.substring(0, sectionMatcher.start()); } public static String getJavadocSection(String javadoc, String sectionNameSpec) { String[] sectionNames = sectionNameSpec.split("\\|"); - Matcher m = SECTION_FINDER.matcher(javadoc); + Matcher sectionMatcher = SECTION_FINDER.matcher(javadoc); + Matcher lineBreakMatcher = LINE_BREAK_FINDER.matcher(javadoc); int sectionStart = -1; int sectionEnd = -1; - while (m.find()) { + while (sectionMatcher.find()) { boolean found = false; - for (String sectionName : sectionNames) if (m.group(1).equalsIgnoreCase(sectionName)) { + for (String sectionName : sectionNames) if (sectionMatcher.group(1).equalsIgnoreCase(sectionName)) { found = true; break; } if (found) { - sectionStart = m.end() + 1; + lineBreakMatcher.find(sectionMatcher.end()); + sectionStart = lineBreakMatcher.end(); } else if (sectionStart != -1) { - sectionEnd = m.start(); + sectionEnd = sectionMatcher.start(); } } diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index b45ac72d..d53856af 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -350,7 +350,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { if (!changed) clearChanged(); } - private static boolean isComplete(CompilationUnitDeclaration unit) { + public static boolean isComplete(CompilationUnitDeclaration unit) { return (unit.bits & ASTNode.HasAllMethodBodies) != 0; } diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index 6fcde937..59a0709e 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2019 The Project Lombok Authors. + * Copyright (C) 2009-2020 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,9 @@ package lombok.eclipse; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; import lombok.ConfigurationKeys; import lombok.core.LombokConfiguration; @@ -63,6 +66,7 @@ public class TransformEclipseAST { public static boolean disableLombok = false; private static final HistogramTracker lombokTracker; + private static Map<CompilationUnitDeclaration, State> transformationStates = Collections.synchronizedMap(new WeakHashMap<CompilationUnitDeclaration, State>()); static { String v = System.getProperty("lombok.histogram"); @@ -130,6 +134,30 @@ public class TransformEclipseAST { } /** + * Check if lombok already handled the given AST. This method will return + * <code>true</code> once for diet mode and once for full mode. + * + * The reason for this is that Eclipse invokes the transform method multiple + * times during compilation and it is enough to transform it once and not + * repeat the whole thing over and over again. + * + * @param ast The AST node belonging to the compilation unit (java speak for a single source file). + * @return <code>true</code> if this AST was already handled by lombok. + */ + public static boolean alreadyTransformed(CompilationUnitDeclaration ast) { + State state = transformationStates.get(ast); + + if (state == State.FULL) return true; + if (state == State.DIET) { + if (!EclipseAST.isComplete(ast)) return true; + transformationStates.put(ast, State.FULL); + } else { + transformationStates.put(ast, State.DIET); + } + return false; + } + + /** * This method is called immediately after Eclipse finishes building a CompilationUnitDeclaration, which is * the top-level AST node when Eclipse parses a source file. The signature is 'magic' - you should not * change it! @@ -144,6 +172,7 @@ public class TransformEclipseAST { if (disableLombok) return; if (Symbols.hasSymbol("lombok.disable")) return; + if (alreadyTransformed(ast)) return; // Do NOT abort if (ast.bits & ASTNode.HasAllMethodBodies) != 0 - that doesn't work. @@ -243,4 +272,9 @@ public class TransformEclipseAST { nextPriority = Math.min(nextPriority, handlers.handleAnnotation(top, annotationNode, annotation, priority)); } } + + private static enum State { + DIET, + FULL + } } diff --git a/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java b/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java index 9343011f..cee3912c 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldNameConstants.java @@ -171,10 +171,10 @@ public class HandleFieldNameConstants extends EclipseAnnotationHandler<FieldName injectMethod(fieldsType, constructor); } + Clinit cli = null; if (genClinit) { - Clinit cli = new Clinit(parent.compilationResult); + cli = new Clinit(parent.compilationResult); injectMethod(fieldsType, cli); - cli.traverse(generatedByVisitor, ((TypeDeclaration) fieldsType.get()).scope); } for (EclipseNode fieldNode : fields) { @@ -202,5 +202,9 @@ public class HandleFieldNameConstants extends EclipseAnnotationHandler<FieldName injectField(fieldsType, constantField); constantField.traverse(generatedByVisitor, ((TypeDeclaration) fieldsType.get()).initializerScope); } + + if (genClinit) { + cli.traverse(generatedByVisitor, ((TypeDeclaration) fieldsType.get()).scope); + } } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 8a1ed5e7..2379d0a0 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -39,6 +39,7 @@ import java.util.regex.Pattern; import javax.lang.model.element.Element; +import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Scope; @@ -1126,7 +1127,25 @@ public class JavacHandlerUtil { } } } - + + static class JCAnnotationReflect { + private static Field ATTRIBUTE; + + static { + try { + ATTRIBUTE = Permit.getField(JCAnnotation.class, "attribute"); + } catch (Exception ignore) {} + } + + static Attribute.Compound getAttribute(JCAnnotation jcAnnotation) { + try { + return (Attribute.Compound) ATTRIBUTE.get(jcAnnotation); + } catch (Exception e) { + return null; + } + } + } + // jdk9 support, types have changed, names stay the same static class ClassSymbolMembersField { private static final Field membersField; @@ -1177,8 +1196,10 @@ public class JavacHandlerUtil { * Also takes care of updating the JavacAST. */ public static void injectMethod(JavacNode typeNode, JCMethodDecl method, List<Type> paramTypes, Type returnType) { + Context context = typeNode.getContext(); + Symtab symtab = Symtab.instance(context); JCClassDecl type = (JCClassDecl) typeNode.get(); - + if (method.getName().contentEquals("<init>")) { //Scan for default constructor, and remove it. int idx = 0; @@ -1195,7 +1216,7 @@ public class JavacHandlerUtil { idx++; } } - + addSuppressWarningsAll(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext()); addGenerated(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext()); type.defs = type.defs.append(method); @@ -1203,18 +1224,38 @@ public class JavacHandlerUtil { List<Symbol.VarSymbol> params = null; if (method.getParameters() != null && !method.getParameters().isEmpty()) { ListBuffer<Symbol.VarSymbol> newParams = new ListBuffer<Symbol.VarSymbol>(); - for (JCTree.JCVariableDecl param : method.getParameters()) { - if (param.sym != null) newParams.append(param.sym); + for (int i = 0; i < method.getParameters().size(); i++) { + JCTree.JCVariableDecl param = method.getParameters().get(i); + if (param.sym == null) { + Type paramType = paramTypes == null ? param.getType().type : paramTypes.get(i); + VarSymbol varSymbol = new VarSymbol(param.mods.flags, param.name, paramType, symtab.noSymbol); + List<JCAnnotation> annotations = param.getModifiers().getAnnotations(); + if (annotations != null && !annotations.isEmpty()) { + ListBuffer<Attribute.Compound> newAnnotations = new ListBuffer<Attribute.Compound>(); + for (JCAnnotation jcAnnotation : annotations) { + Attribute.Compound attribute = JCAnnotationReflect.getAttribute(jcAnnotation); + if (attribute != null) { + newAnnotations.append(attribute); + } + } + if (annotations.length() == newAnnotations.length()) { + varSymbol.appendAttributes(newAnnotations.toList()); + } + } + newParams.append(varSymbol); + } else { + newParams.append(param.sym); + } } params = newParams.toList(); if (params.length() != method.getParameters().length()) params = null; } fixMethodMirror(typeNode.getContext(), typeNode.getElement(), method.getModifiers().flags, method.getName(), paramTypes, params, returnType); - + typeNode.add(method, Kind.METHOD); } - + private static void fixMethodMirror(Context context, Element typeMirror, long access, Name methodName, List<Type> paramTypes, List<Symbol.VarSymbol> params, Type returnType) { if (typeMirror == null || paramTypes == null || returnType == null) return; ClassSymbol cs = (ClassSymbol) typeMirror; diff --git a/src/delombok/lombok/delombok/PrettyPrinter.java b/src/delombok/lombok/delombok/PrettyPrinter.java index 208b215f..54fa4ebf 100644 --- a/src/delombok/lombok/delombok/PrettyPrinter.java +++ b/src/delombok/lombok/delombok/PrettyPrinter.java @@ -188,12 +188,6 @@ public class PrettyPrinter extends JCTree.Visitor { return getEndPosition(tree, compilationUnit); } - private static int lineEndPos(String s, int start) { - int pos = s.indexOf('\n', start); - if (pos < 0) pos = s.length(); - return pos; - } - private boolean needsAlign, needsNewLine, onNewLine = true, needsSpace, aligned; public static final class UncheckedIOException extends RuntimeException { @@ -434,23 +428,25 @@ public class PrettyPrinter extends JCTree.Visitor { private void printDocComment(JCTree tree) { String dc = getJavadocFor(tree); if (dc == null) return; + aPrintln("/**"); - int pos = 0; - int endpos = lineEndPos(dc, pos); boolean atStart = true; - while (pos < dc.length()) { - String line = dc.substring(pos, endpos); - if (line.trim().isEmpty() && atStart) { + + for (String line : dc.split("\\r?\\n")) { + if (atStart && line.trim().isEmpty()) { atStart = false; continue; } + atStart = false; aPrint(" *"); - if (pos < dc.length() && dc.charAt(pos) > ' ') print(" "); - println(dc.substring(pos, endpos)); - pos = endpos + 1; - endpos = lineEndPos(dc, pos); + if (!line.isEmpty() && !Character.isWhitespace(line.charAt(0))) { + print(" "); + } + + println(line); } + aPrintln(" */"); } diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java index 056852c8..f22e78a8 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchVal.java @@ -35,11 +35,13 @@ import org.eclipse.jdt.internal.compiler.ast.FunctionalExpression; import org.eclipse.jdt.internal.compiler.ast.ImportReference; import org.eclipse.jdt.internal.compiler.ast.LambdaExpression; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; @@ -227,14 +229,16 @@ public class PatchVal { boolean var = isVar(local, scope); if (!(val || var)) return false; - StackTraceElement[] st = new Throwable().getStackTrace(); - for (int i = 0; i < st.length - 2 && i < 10; i++) { - if (st[i].getClassName().equals("lombok.launch.PatchFixesHider$Val")) { - boolean valInForStatement = val && - st[i + 1].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") && - st[i + 2].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.ForStatement"); - if (valInForStatement) return false; - break; + if (val) { + StackTraceElement[] st = new Throwable().getStackTrace(); + for (int i = 0; i < st.length - 2 && i < 10; i++) { + if (st[i].getClassName().equals("lombok.launch.PatchFixesHider$Val")) { + boolean valInForStatement = + st[i + 1].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") && + st[i + 2].getClassName().equals("org.eclipse.jdt.internal.compiler.ast.ForStatement"); + if (valInForStatement) return false; + break; + } } } @@ -264,6 +268,7 @@ public class PatchVal { } TypeBinding resolved = null; + Constant oldConstant = init.constant; try { resolved = decomponent ? getForEachComponentType(init, scope) : resolveForExpression(init, scope); } catch (NullPointerException e) { @@ -280,6 +285,10 @@ public class PatchVal { } catch (Exception e) { // Some type thing failed. } + } else { + if (init instanceof MessageSend && ((MessageSend) init).actualReceiverType == null) { + init.constant = oldConstant; + } } } diff --git a/src/stubs/com/sun/tools/javac/code/Symbol.java b/src/stubs/com/sun/tools/javac/code/Symbol.java index 7324cb8e..15b04148 100644 --- a/src/stubs/com/sun/tools/javac/code/Symbol.java +++ b/src/stubs/com/sun/tools/javac/code/Symbol.java @@ -24,7 +24,7 @@ import com.sun.tools.javac.util.Name; public abstract class Symbol implements Element { public Type type; public Name name; - + public long flags() { return 0; } public boolean isStatic() { return false; } public boolean isConstructor() { return false; } @@ -38,7 +38,9 @@ public abstract class Symbol implements Element { @Override public Name getSimpleName() { return null; } @Override public java.util.List<Symbol> getEnclosedElements() { return null; } @Override public Element getEnclosingElement() { return null; } - + public void appendAttributes(List<Attribute.Compound> l) { + } + public static abstract class TypeSymbol extends Symbol {} public static class MethodSymbol extends Symbol implements ExecutableElement { @@ -60,6 +62,8 @@ public abstract class Symbol implements Element { public static class VarSymbol extends Symbol implements VariableElement { public Type type; + public VarSymbol(long flags, Name name, Type type, Symbol owner) { + } @Override public ElementKind getKind() { return null; } @Override public Set<Modifier> getModifiers() { return null; } @Override public <R, P> R accept(ElementVisitor<R, P> v, P p) { return null; } diff --git a/src/stubs/com/sun/tools/javac/code/Symtab.java b/src/stubs/com/sun/tools/javac/code/Symtab.java index 2b524e4c..8d823531 100644 --- a/src/stubs/com/sun/tools/javac/code/Symtab.java +++ b/src/stubs/com/sun/tools/javac/code/Symtab.java @@ -5,6 +5,7 @@ package com.sun.tools.javac.code; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.ModuleSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.util.Context; public class Symtab { @@ -14,6 +15,7 @@ public class Symtab { public Type objectType; public static Symtab instance(Context context) {return null;} public Type unknownType; + public TypeSymbol noSymbol; // JDK 9 public ModuleSymbol unnamedModule; diff --git a/src/support/lombok/website/RunSite.java b/src/support/lombok/website/RunSite.java new file mode 100644 index 00000000..17e158c0 --- /dev/null +++ b/src/support/lombok/website/RunSite.java @@ -0,0 +1,103 @@ +package lombok.website; + +import static spark.Spark.*; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import spark.Request; +import spark.Response; +import spark.Route; + +public class RunSite { + private static final int DEFAULT_PORT = 4569; + private final Path base; + + public RunSite(Path base) { + this.base = base; + } + + public static void main(String[] args) throws Exception { + boolean open = args.length > 1 && args[1].equals("open"); + new RunSite(Paths.get(args[0])).go(open); + } + + private void go(boolean open) throws Exception { + port(DEFAULT_PORT); + get("/", serve("main.html")); + get("/setup/overview", serve("setup/main.html")); + get("/setup", serve("setup/main.html")); + get("/features", serve("features/index.html")); + get("/features/all", serve("features/index.html")); + get("/features/experimental/all", serve("features/experimental/index.html")); + get("/features/experimental", serve("features/experimental/index.html")); + + serveDir("/", base); + + System.out.println("Serving page from " + base + " -- hit enter to stop"); + if (open) Opener.open("http://localhost:" + DEFAULT_PORT + "/"); + System.in.read(); + System.exit(0); + } + + private void serveDir(String sub, Path dir) throws IOException { + DirectoryStream<Path> ds = Files.newDirectoryStream(dir); + try { + for (Path c : ds) { + String n = c.getFileName().toString(); + if (n.equals(".") || n.equals("..")) continue; + if (Files.isDirectory(c)) { + serveDir(sub + n + "/", c); + continue; + } + String rel = base.relativize(c).toString(); + get(sub + n, serve(rel)); + if (n.endsWith(".html")) get(sub + n.substring(0, n.length() - 5), serve(rel)); + } + } finally { + ds.close(); + } + } + + private static class Opener { + public static void open(String url) throws Exception { + Desktop.getDesktop().browse(new URI(url)); + } + } + + private Route serve(final String path) { + final Path tgt = base.resolve(path); + + return new Route() { + @Override public Object handle(Request req, Response res) throws Exception { + res.type(mapMime(path)); + return Files.readAllBytes(tgt); + } + }; + } + + private String mapMime(String path) { + if (path.endsWith(".css")) return "text/css; charset=UTF-8"; + if (path.endsWith(".js")) return "text/javascript; charset=UTF-8"; + if (path.endsWith(".png")) return "image/png"; + if (path.endsWith(".gif")) return "image/gif"; + if (path.endsWith(".jpg")) return "image/jpeg"; + if (path.endsWith(".mp4")) return "video/mp4"; + if (path.endsWith(".m4v")) return "video/mp4"; + if (path.endsWith(".ogv")) return "video/ogg"; + if (path.endsWith(".webm")) return "video/webm"; + if (path.endsWith(".ico")) return "image/x-icon"; + if (path.endsWith(".pdf")) return "application/pdf"; + if (path.endsWith(".json")) return "application/json"; + if (path.endsWith(".xml")) return "text/xml"; + if (path.endsWith(".woff")) return "font/woff"; + if (path.endsWith(".woff2")) return "font/woff2"; + if (path.endsWith(".html")) return "text/html; charset=UTF-8"; + return "text/plain; charset=UTF-8"; + } +} |