http://git-wip-us.apache.org/repos/asf/hadoop/blob/38c6fa5c/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletGen.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletGen.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletGen.java new file mode 100644 index 0000000..c6ca93c --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletGen.java @@ -0,0 +1,449 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.hadoop.yarn.webapp.hamlet2; + +import com.google.common.collect.Sets; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.util.StringUtils; +import org.apache.hadoop.yarn.webapp.WebAppException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Generates a specific hamlet implementation class from a spec class + * using a generic hamlet implementation class. + */ +@InterfaceAudience.LimitedPrivate({"YARN", "MapReduce"}) +public class HamletGen { + static final Logger LOG = LoggerFactory.getLogger(HamletGen.class); + static final Options opts = new Options(); + static { + opts.addOption("h", "help", false, "Print this help message"). + addOption("s", "spec-class", true, + "The class that holds the spec interfaces. e.g. HamletSpec"). + addOption("i", "impl-class", true, + "An implementation class. e.g. HamletImpl"). + addOption("o", "output-class", true, "Output class name"). + addOption("p", "output-package", true, "Output package name"); + }; + + static final Pattern elementRegex = Pattern.compile("^[A-Z][A-Z0-9]*$"); + + int bytes = 0; + PrintWriter out; + final Set<String> endTagOptional = Sets.newHashSet(); + final Set<String> inlineElements = Sets.newHashSet(); + Class<?> top; // html top-level interface + String hamlet; // output class simple name; + boolean topMode; + + /** + * Generate a specific Hamlet implementation from a spec. + * @param specClass holds hamlet interfaces. e.g. {@link HamletSpec} + * @param implClass a generic hamlet implementation. e.g. {@link HamletImpl} + * @param outputName name of the output class. e.g. {@link Hamlet} + * @param outputPkg package name of the output class. + * @throws IOException + */ + public void generate(Class<?> specClass, Class<?> implClass, + String outputName, String outputPkg) throws IOException { + LOG.info("Generating {} using {} and {}", new Object[]{outputName, + specClass, implClass}); + out = new PrintWriter(outputName +".java", "UTF-8"); + hamlet = basename(outputName); + String pkg = pkgName(outputPkg, implClass.getPackage().getName()); + puts(0, "// Generated by HamletGen. Do NOT edit!\n", + "package ", pkg, ";\n", + "import java.io.PrintWriter;\n", + "import java.util.EnumSet;\n", + "import static java.util.EnumSet.*;\n", + "import static ", implClass.getName(), ".EOpt.*;\n", + "import org.apache.hadoop.yarn.webapp.SubView;"); + String implClassName = implClass.getSimpleName(); + if (!implClass.getPackage().getName().equals(pkg)) { + puts(0, "import ", implClass.getName(), ';'); + } + puts(0, "\n", + "public class ", hamlet, " extends ", implClassName, + " implements ", specClass.getSimpleName(), "._Html {\n", + " public ", hamlet, "(PrintWriter out, int nestLevel,", + " boolean wasInline) {\n", + " super(out, nestLevel, wasInline);\n", + " }\n\n", // inline is context sensitive + " static EnumSet<EOpt> opt(boolean endTag, boolean inline, ", + "boolean pre) {\n", + " EnumSet<EOpt> opts = of(ENDTAG);\n", + " if (!endTag) opts.remove(ENDTAG);\n", + " if (inline) opts.add(INLINE);\n", + " if (pre) opts.add(PRE);\n", + " return opts;\n", + " }"); + initLut(specClass); + genImpl(specClass, implClassName, 1); + LOG.info("Generating {} methods", hamlet); + genMethods(hamlet, top, 1); + puts(0, "}"); + out.close(); + LOG.info("Wrote {} bytes to {}.java", bytes, outputName); + } + + String basename(String path) { + return path.substring(path.lastIndexOf('/') + 1); + } + + String pkgName(String pkg, String defaultPkg) { + if (pkg == null || pkg.isEmpty()) return defaultPkg; + return pkg; + } + + void initLut(Class<?> spec) { + endTagOptional.clear(); + inlineElements.clear(); + for (Class<?> cls : spec.getClasses()) { + Annotation a = cls.getAnnotation(HamletSpec.Element.class); + if (a != null && !((HamletSpec.Element) a).endTag()) { + endTagOptional.add(cls.getSimpleName()); + } + if (cls.getSimpleName().equals("Inline")) { + for (Method method : cls.getMethods()) { + String retName = method.getReturnType().getSimpleName(); + if (isElement(retName)) { + inlineElements.add(retName); + } + } + } + } + } + + void genImpl(Class<?> spec, String implClassName, int indent) { + String specName = spec.getSimpleName(); + for (Class<?> cls : spec.getClasses()) { + String className = cls.getSimpleName(); + if (cls.isInterface()) { + genFactoryMethods(cls, indent); + } + if (isElement(className)) { + LOG.info("Generating class {}<T>", className); + puts(indent, "\n", + "public class ", className, "<T extends __>", + " extends EImp<T> implements ", specName, ".", className, " {\n", + " public ", className, "(String name, T parent,", + " EnumSet<EOpt> opts) {\n", + " super(name, parent, opts);\n", + " }"); + genMethods(className, cls, indent + 1); + puts(indent, "}"); + } else if (className.equals("_Html")) { + top = cls; + } + } + } + + void genFactoryMethods(Class<?> cls, int indent) { + for (Method method : cls.getDeclaredMethods()) { + String retName = method.getReturnType().getSimpleName(); + String methodName = method.getName(); + if (methodName.charAt(0) == '$') continue; + if (isElement(retName) && method.getParameterTypes().length == 0) { + genFactoryMethod(retName, methodName, indent); + } + } + } + + void genMethods(String className, Class<?> cls, int indent) { + topMode = (top != null && cls.equals(top)); + for (Method method : cls.getMethods()) { + String retName = method.getReturnType().getSimpleName(); + if (method.getName().charAt(0) == '$') { + genAttributeMethod(className, method, indent); + } else if (isElement(retName)) { + genNewElementMethod(className, method, indent); + } else { + genCurElementMethod(className, method, indent); + } + } + } + + void genAttributeMethod(String className, Method method, int indent) { + String methodName = method.getName(); + String attrName = methodName.substring(1).replace("__", "-"); + Type[] params = method.getGenericParameterTypes(); + echo(indent, "\n", + "@Override\n", + "public ", className, topMode ? " " : "<T> ", methodName, "("); + if (params.length == 0) { + puts(0, ") {"); + puts(indent, + " addAttr(\"", attrName, "\", null);\n", + " return this;\n", "}"); + } else if (params.length == 1) { + String typeName = getTypeName(params[0]); + puts(0, typeName, " value) {"); + if (typeName.equals("EnumSet<LinkType>")) { + puts(indent, + " addRelAttr(\"", attrName, "\", value);\n", + " return this;\n", "}"); + } else if (typeName.equals("EnumSet<Media>")) { + puts(indent, + " addMediaAttr(\"", attrName, "\", value);\n", + " return this;\n", "}"); + } else { + puts(indent, + " addAttr(\"", attrName, "\", value);\n", + " return this;\n", "}"); + } + } else { + throwUnhandled(className, method); + } + } + + String getTypeName(Type type) { + if (type instanceof Class<?>) { + return ((Class<?>)type).getSimpleName(); + } + ParameterizedType pt = (ParameterizedType) type; + return ((Class<?>)pt.getRawType()).getSimpleName() +"<"+ + ((Class<?>)pt.getActualTypeArguments()[0]).getSimpleName() +">"; + } + + void genFactoryMethod(String retName, String methodName, int indent) { + puts(indent, "\n", + "private <T extends __> ", retName, "<T> ", methodName, + "__(T e, boolean inline) {\n", + " return new ", retName, "<T>(\"", StringUtils.toLowerCase(retName), + "\", e, opt(", !endTagOptional.contains(retName), ", inline, ", + retName.equals("PRE"), ")); }"); + } + + void genNewElementMethod(String className, Method method, int indent) { + String methodName = method.getName(); + String retName = method.getReturnType().getSimpleName(); + Class<?>[] params = method.getParameterTypes(); + echo(indent, "\n", + "@Override\n", + "public ", retName, "<", className, topMode ? "> " : "<T>> ", + methodName, "("); + if (params.length == 0) { + puts(0, ") {"); + puts(indent, + topMode ? "" : " closeAttrs();\n", + " return ", StringUtils.toLowerCase(retName), "__" + "(this, ", + isInline(className, retName), ");\n", "}"); + } else if (params.length == 1) { + puts(0, "String selector) {"); + puts(indent, + " return setSelector(", methodName, "(), selector);\n", "}"); + } else { + throwUnhandled(className, method); + } + } + + boolean isInline(String container, String className) { + if ((container.equals("BODY") || container.equals(hamlet) || + container.equals("HEAD") || container.equals("HTML")) && + (className.equals("INS") || className.equals("DEL") || + className.equals("SCRIPT"))) { + return false; + } + return inlineElements.contains(className); + } + + void genCurElementMethod(String className, Method method, int indent) { + String methodName = method.getName(); + Class<?>[] params = method.getParameterTypes(); + if (topMode || params.length > 0) { + echo(indent, "\n", + "@Override\n", + "public ", className, topMode ? " " : "<T> ", methodName, "("); + } + if (params.length == 0) { + if (topMode) { + puts(0, ") {"); + puts(indent, " return this;\n", "}"); + } + } else if (params.length == 1) { + if (methodName.equals("base")) { + puts(0, "String href) {"); + puts(indent, + " return base().$href(href).__();\n", "}"); + } else if (methodName.equals("script")) { + puts(0, "String src) {"); + puts(indent, + " return setScriptSrc(script(), src).__();\n", "}"); + } else if (methodName.equals("style")) { + puts(0, "Object... lines) {"); + puts(indent, + " return style().$type(\"text/css\").__(lines).__();\n", "}"); + } else if (methodName.equals("img")) { + puts(0, "String src) {"); + puts(indent, + " return ", methodName, "().$src(src).__();\n", "}"); + } else if (methodName.equals("br") || methodName.equals("hr") || + methodName.equals("col")) { + puts(0, "String selector) {"); + puts(indent, + " return setSelector(", methodName, "(), selector).__();\n", "}"); + } else if (methodName.equals("link")) { + puts(0, "String href) {"); + puts(indent, + " return setLinkHref(", methodName, "(), href).__();\n", "}"); + } else if (methodName.equals("__")) { + if (params[0].getSimpleName().equals("Class")) { + puts(0, "Class<? extends SubView> cls) {"); + puts(indent, + " ", topMode ? "subView" : "_v", "(cls);\n", + " return this;\n", "}"); + } else { + puts(0, "Object... lines) {"); + puts(indent, + " _p(", needsEscaping(className), ", lines);\n", + " return this;\n", "}"); + } + } else if (methodName.equals("_r")) { + puts(0, "Object... lines) {"); + puts(indent, + " _p(false, lines);\n", + " return this;\n", "}"); + } else { + puts(0, "String cdata) {"); + puts(indent, + " return ", methodName, "().__(cdata).__();\n", "}"); + } + } else if (params.length == 2) { + if (methodName.equals("meta")) { + puts(0, "String name, String content) {"); + puts(indent, + " return meta().$name(name).$content(content).__();\n", "}"); + } else if (methodName.equals("meta_http")) { + puts(0, "String header, String content) {"); + puts(indent, + " return meta().$http_equiv(header).$content(content).__();\n", + "}"); + } else if (methodName.equals("a")) { + puts(0, "String href, String anchorText) {"); + puts(indent, + " return a().$href(href).__(anchorText).__();\n", "}"); + } else if (methodName.equals("bdo")) { + puts(0, "Dir dir, String cdata) {"); + puts(indent, " return bdo().$dir(dir).__(cdata).__();\n", "}"); + } else if (methodName.equals("label")) { + puts(0, "String forId, String cdata) {"); + puts(indent, " return label().$for(forId).__(cdata).__();\n", "}"); + } else if (methodName.equals("param")) { + puts(0, "String name, String value) {"); + puts(indent, + " return param().$name(name).$value(value).__();\n", "}"); + } else { + puts(0, "String selector, String cdata) {"); + puts(indent, + " return setSelector(", methodName, + "(), selector).__(cdata).__();\n", "}"); + } + } else if (params.length == 3) { + if (methodName.equals("a")) { + puts(0, "String selector, String href, String anchorText) {"); + puts(indent, + " return setSelector(a(), selector)", + ".$href(href).__(anchorText).__();\n", "}"); + } + } else { + throwUnhandled(className, method); + } + } + + static boolean needsEscaping(String eleName) { + return !eleName.equals("SCRIPT") && !eleName.equals("STYLE"); + } + + static void throwUnhandled(String className, Method method) { + throw new WebAppException("Unhandled " + className + "#" + method); + } + + void echo(int indent, Object... args) { + String prev = null; + for (Object o : args) { + String s = String.valueOf(o); + if (!s.isEmpty() && !s.equals("\n") && + (prev == null || prev.endsWith("\n"))) { + indent(indent); + } + prev = s; + out.print(s); + bytes += s.length(); + } + } + + void indent(int indent) { + for (int i = 0; i < indent; ++i) { + out.print(" "); + bytes += 2; + } + } + + void puts(int indent, Object... args) { + echo(indent, args); + out.println(); + ++bytes; + } + + boolean isElement(String s) { + return elementRegex.matcher(s).matches(); + } + + public static void main(String[] args) throws Exception { + CommandLine cmd = new GnuParser().parse(opts, args); + if (cmd.hasOption("help")) { + new HelpFormatter().printHelp("Usage: hbgen [OPTIONS]", opts); + return; + } + // defaults + Class<?> specClass = HamletSpec.class; + Class<?> implClass = HamletImpl.class; + String outputClass = "HamletTmp"; + String outputPackage = implClass.getPackage().getName(); + if (cmd.hasOption("spec-class")) { + specClass = Class.forName(cmd.getOptionValue("spec-class")); + } + if (cmd.hasOption("impl-class")) { + implClass = Class.forName(cmd.getOptionValue("impl-class")); + } + if (cmd.hasOption("output-class")) { + outputClass = cmd.getOptionValue("output-class"); + } + if (cmd.hasOption("output-package")) { + outputPackage = cmd.getOptionValue("output-package"); + } + new HamletGen().generate(specClass, implClass, outputClass, outputPackage); + } +}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/38c6fa5c/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletImpl.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletImpl.java new file mode 100644 index 0000000..995e9fb --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/hamlet2/HamletImpl.java @@ -0,0 +1,385 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.hadoop.yarn.webapp.hamlet2; + +import com.google.common.base.Joiner; +import static com.google.common.base.Preconditions.*; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; + +import java.io.PrintWriter; +import java.util.EnumSet; +import static java.util.EnumSet.*; +import java.util.Iterator; + +import static org.apache.commons.lang.StringEscapeUtils.*; +import static org.apache.hadoop.yarn.webapp.hamlet2.HamletImpl.EOpt.*; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.yarn.webapp.SubView; +import org.apache.hadoop.yarn.webapp.WebAppException; + + +/** + * A simple unbuffered generic hamlet implementation. + * + * Zero copy but allocation on every element, which could be + * optimized to use a thread-local element pool. + * + * Prints HTML as it builds. So the order is important. + */ +@InterfaceAudience.Private +public class HamletImpl extends HamletSpec { + private static final String INDENT_CHARS = " "; + private static final Splitter SS = Splitter.on('.'). + omitEmptyStrings().trimResults(); + private static final Joiner SJ = Joiner.on(' '); + private static final Joiner CJ = Joiner.on(", "); + static final int S_ID = 0; + static final int S_CLASS = 1; + + int nestLevel; + int indents; // number of indent() called. mostly for testing. + private final PrintWriter out; + private final StringBuilder sb = new StringBuilder(); // not shared + private boolean wasInline = false; + + /** + * Element options. (whether it needs end tag, is inline etc.) + */ + public enum EOpt { + /** needs end(close) tag */ + ENDTAG, + /** The content is inline */ + INLINE, + /** The content is preformatted */ + PRE + }; + + /** + * The base class for elements + * @param <T> type of the parent (containing) element for the element + */ + public class EImp<T extends __> implements _Child { + private final String name; + private final T parent; // short cut for parent element + private final EnumSet<EOpt> opts; // element options + + private boolean started = false; + private boolean attrsClosed = false; + + EImp(String name, T parent, EnumSet<EOpt> opts) { + this.name = name; + this.parent = parent; + this.opts = opts; + } + + @Override + public T __() { + closeAttrs(); + --nestLevel; + printEndTag(name, opts); + return parent; + } + + protected void _p(boolean quote, Object... args) { + closeAttrs(); + for (Object s : args) { + if (!opts.contains(PRE)) { + indent(opts); + } + out.print(quote ? escapeHtml(String.valueOf(s)) + : String.valueOf(s)); + if (!opts.contains(INLINE) && !opts.contains(PRE)) { + out.println(); + } + } + } + + protected void _v(Class<? extends SubView> cls) { + closeAttrs(); + subView(cls); + } + + protected void closeAttrs() { + if (!attrsClosed) { + startIfNeeded(); + ++nestLevel; + out.print('>'); + if (!opts.contains(INLINE) && !opts.contains(PRE)) { + out.println(); + } + attrsClosed = true; + } + } + + protected void addAttr(String name, String value) { + checkState(!attrsClosed, "attribute added after content"); + startIfNeeded(); + printAttr(name, value); + } + + protected void addAttr(String name, Object value) { + addAttr(name, String.valueOf(value)); + } + + protected void addMediaAttr(String name, EnumSet<Media> media) { + // 6.13 comma-separated list + addAttr(name, CJ.join(media)); + } + + protected void addRelAttr(String name, EnumSet<LinkType> types) { + // 6.12 space-separated list + addAttr(name, SJ.join(types)); + } + + private void startIfNeeded() { + if (!started) { + printStartTag(name, opts); + started = true; + } + } + + protected void _inline(boolean choice) { + if (choice) { + opts.add(INLINE); + } else { + opts.remove(INLINE); + } + } + + protected void _endTag(boolean choice) { + if (choice) { + opts.add(ENDTAG); + } else { + opts.remove(ENDTAG); + } + } + + protected void _pre(boolean choice) { + if (choice) { + opts.add(PRE); + } else { + opts.remove(PRE); + } + } + } + + public class Generic<T extends __> extends EImp<T> implements PCData { + Generic(String name, T parent, EnumSet<EOpt> opts) { + super(name, parent, opts); + } + + public Generic<T> _inline() { + super._inline(true); + return this; + } + + public Generic<T> _noEndTag() { + super._endTag(false); + return this; + } + + public Generic<T> _pre() { + super._pre(true); + return this; + } + + public Generic<T> _attr(String name, String value) { + addAttr(name, value); + return this; + } + + public Generic<Generic<T>> _elem(String name, EnumSet<EOpt> opts) { + closeAttrs(); + return new Generic<Generic<T>>(name, this, opts); + } + + public Generic<Generic<T>> elem(String name) { + return _elem(name, of(ENDTAG)); + } + + @Override + public Generic<T> __(Object... lines) { + _p(true, lines); + return this; + } + + @Override + public Generic<T> _r(Object... lines) { + _p(false, lines); + return this; + } + } + + public HamletImpl(PrintWriter out, int nestLevel, boolean wasInline) { + this.out = out; + this.nestLevel = nestLevel; + this.wasInline = wasInline; + } + + public int nestLevel() { + return nestLevel; + } + + public boolean wasInline() { + return wasInline; + } + + public void setWasInline(boolean state) { + wasInline = state; + } + + public PrintWriter getWriter() { + return out; + } + + /** + * Create a root-level generic element. + * Mostly for testing purpose. + * @param <T> type of the parent element + * @param name of the element + * @param opts {@link EOpt element options} + * @return the element + */ + public <T extends __> + Generic<T> root(String name, EnumSet<EOpt> opts) { + return new Generic<T>(name, null, opts); + } + + public <T extends __> Generic<T> root(String name) { + return root(name, of(ENDTAG)); + } + + protected void printStartTag(String name, EnumSet<EOpt> opts) { + indent(opts); + sb.setLength(0); + out.print(sb.append('<').append(name).toString()); // for easier mock test + } + + protected void indent(EnumSet<EOpt> opts) { + if (opts.contains(INLINE) && wasInline) { + return; + } + if (wasInline) { + out.println(); + } + wasInline = opts.contains(INLINE) || opts.contains(PRE); + for (int i = 0; i < nestLevel; ++i) { + out.print(INDENT_CHARS); + } + ++indents; + } + + protected void printEndTag(String name, EnumSet<EOpt> opts) { + if (!opts.contains(ENDTAG)) { + return; + } + if (!opts.contains(PRE)) { + indent(opts); + } else { + wasInline = opts.contains(INLINE); + } + sb.setLength(0); + out.print(sb.append("</").append(name).append('>').toString()); // ditto + if (!opts.contains(INLINE)) { + out.println(); + } + } + + protected void printAttr(String name, String value) { + sb.setLength(0); + sb.append(' ').append(name); + if (value != null) { + sb.append("=\"").append(escapeHtml(value)).append("\""); + } + out.print(sb.toString()); + } + + /** + * Sub-classes should override this to do something interesting. + * @param cls the sub-view class + */ + protected void subView(Class<? extends SubView> cls) { + indent(of(ENDTAG)); // not an inline view + sb.setLength(0); + out.print(sb.append('[').append(cls.getName()).append(']').toString()); + out.println(); + } + + /** + * Parse selector into id and classes + * @param selector in the form of (#id)?(.class)* + * @return an two element array [id, "space-separated classes"]. + * Either element could be null. + * @throws WebAppException when both are null or syntax error. + */ + public static String[] parseSelector(String selector) { + String[] result = new String[]{null, null}; + Iterable<String> rs = SS.split(selector); + Iterator<String> it = rs.iterator(); + if (it.hasNext()) { + String maybeId = it.next(); + if (maybeId.charAt(0) == '#') { + result[S_ID] = maybeId.substring(1); + if (it.hasNext()) { + result[S_CLASS] = SJ.join(Iterables.skip(rs, 1)); + } + } else { + result[S_CLASS] = SJ.join(rs); + } + return result; + } + throw new WebAppException("Error parsing selector: "+ selector); + } + + /** + * Set id and/or class attributes for an element. + * @param <E> type of the element + * @param e the element + * @param selector Haml form of "(#id)?(.class)*" + * @return the element + */ + public static <E extends CoreAttrs> E setSelector(E e, String selector) { + String[] res = parseSelector(selector); + if (res[S_ID] != null) { + e.$id(res[S_ID]); + } + if (res[S_CLASS] != null) { + e.$class(res[S_CLASS]); + } + return e; + } + + public static <E extends LINK> E setLinkHref(E e, String href) { + if (href.endsWith(".css")) { + e.$rel("stylesheet"); // required in html5 + } + e.$href(href); + return e; + } + + public static <E extends SCRIPT> E setScriptSrc(E e, String src) { + if (src.endsWith(".js")) { + e.$type("text/javascript"); // required in html4 + } + e.$src(src); + return e; + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-commits-h...@hadoop.apache.org