package org.richfaces.bootstrap.less; import java.io.IOException; import java.io.Writer; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.ResponseWriter; import javax.faces.context.ResponseWriterWrapper; public class LessResponseWriter extends ResponseWriterWrapper { private ResponseWriter wrapped; private boolean insideLink = false; private LessStylesheetDetector detector; public LessResponseWriter(ResponseWriter wrapped) { this.wrapped = wrapped; } @Override public ResponseWriter cloneWithWriter(Writer writer) { return new LessResponseWriter(super.cloneWithWriter(writer)); } @Override public void startElement(String name, UIComponent component) throws IOException { super.startElement(name, component); if ("link".equals(name)) { insideLink = true; detector = new LessStylesheetDetector(); } else { insideLink = false; } } private void writeAttributesFinally(ResponseWriter writer) throws IOException { if (insideLink) { detector.writeAttributes(writer); } } @Override public void endElement(String name) throws IOException { writeAttributesFinally(wrapped); super.endElement(name); if ("link".equals(name)) { insideLink = false; } } /** * An override which checks if an attribute of <code>type="text"</code> is been written by an {@link UIInput} component and * if so then check if the <code>type</code> attribute isn't been explicitly set by the developer and if so then write it. * * @throws IllegalArgumentException When the <code>type</code> attribute is not supported. */ @Override public void writeAttribute(String name, Object value, String property) throws IOException { if (insideLink) { detector.putAttribute(name, value, property); } else { super.writeAttribute(name, value, property); } } @Override public void writeURIAttribute(String name, Object value, String property) throws IOException { if (insideLink) { detector.putURIAttribute(name, value, property); } else { super.writeURIAttribute(name, value, property); } } @Override public ResponseWriter getWrapped() { return wrapped; } @Override public void writeText(char[] text, int off, int len) throws IOException { writeAttributesFinally(wrapped); super.writeText(text, off, len); } @Override public void writeText(Object text, String property) throws IOException { writeAttributesFinally(wrapped); super.writeText(text, property); } @Override public void writeText(Object text, UIComponent component, String property) throws IOException { writeAttributesFinally(wrapped); super.writeText(text, component, property); } @Override public void writeComment(Object comment) throws IOException { writeAttributesFinally(wrapped); super.writeComment(comment); } private static class LessStylesheetDetector { private Map<String, ValuePropertyPair> attributes = new LinkedHashMap<String, ValuePropertyPair>(); private void putAttribute(String name, Object value, String property) { ValuePropertyPair pair = new ValuePropertyPair(property, value, false); attributes.put(name, pair); } private void putURIAttribute(String name, Object value, String property) { ValuePropertyPair pair = new ValuePropertyPair(property, value, true); attributes.put(name, pair); } private boolean isLessStylesheet() { ValuePropertyPair typePair = attributes.get("type"); ValuePropertyPair relPair = attributes.get("rel"); ValuePropertyPair hrefPair = attributes.get("href"); if (typePair == null || relPair == null || hrefPair == null) { return false; } return typePair.valueMatches("text/css") && relPair.valueMatches("stylesheet") && hrefPair.valueContains(".less"); } private void writeAttributes(ResponseWriter writer) throws IOException { boolean isStylesheet = isLessStylesheet(); for (Entry<String, ValuePropertyPair> attribute : attributes.entrySet()) { String name = attribute.getKey(); ValuePropertyPair pair = attribute.getValue(); Object value = pair.getValue(); String property = pair.getProperty(); boolean isUri = pair.isUri(); if ("rel".equals(name) && !isUri && isStylesheet) { writer.writeAttribute(name, "stylesheet/less", property); } else { if (isUri) { writer.writeURIAttribute(name, value, property); } else { writer.writeAttribute(name, value, property); } } } } private class ValuePropertyPair { private String property; private Object value; private boolean isUri; public ValuePropertyPair(String property, Object value, boolean isUri) { this.property = property; this.value = value; this.isUri = isUri; } public String getProperty() { return property; } public Object getValue() { return value; } public boolean isUri() { return isUri; } public boolean valueMatches(String expected) { if (value == null) { return false; } return value.toString().equals(expected); } public boolean valueContains(String expected) { if (value == null) { return false; } return value.toString().contains(expected); } } } }