blob: d3e5ae35e417b206ee7b7bca1b8f20755d68903c [file] [log] [blame]
// Copyright (C) 2009 The Android Open Source Project
//
// 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.gwtexpui.linker.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
/**
* Selects a permutation based on the HTTP request.
* <p>
* To use this class the application's GWT module must include our linker by
* inheriting our module:
*
* <pre>
* &lt;inherits name='com.google.gwtexpui.linker.ServerPlannedIFrameLinker'/&gt;
* </pre>
*/
public class PermutationSelector {
private final String moduleName;
private final Map<String, Rule> rulesByName;
private final List<Rule> ruleOrder;
private final List<Permutation> permutations;
private final List<String> css;
/**
* Create an empty selector for a module.
* <p>
* {@link UserAgentRule} rule is automatically registered. Additional custom
* selector rules may be registered before {@link #init(ServletContext)} is
* called to finish the selector setup.
*
* @param moduleName the name of the module within the context.
*/
public PermutationSelector(final String moduleName) {
this.moduleName = moduleName;
this.rulesByName = new HashMap<String, Rule>();
this.ruleOrder = new ArrayList<Rule>();
this.permutations = new ArrayList<Permutation>();
this.css = new ArrayList<String>();
register(new UserAgentRule());
}
private void notInitialized() {
if (!ruleOrder.isEmpty()) {
throw new IllegalStateException("Already initialized");
}
}
/**
* Register a property selection rule.
*
* @param r the rule implementation.
*/
public void register(Rule r) {
notInitialized();
rulesByName.put(r.getName(), r);
}
/**
* Initialize the selector by reading the module's {@code permutations} file.
*
* @param ctx context to load the module data from.
* @throws ServletException
* @throws IOException
*/
public void init(ServletContext ctx) throws ServletException, IOException {
notInitialized();
final String tableName = "/" + moduleName + "/permutations";
final InputStream in = ctx.getResourceAsStream(tableName);
if (in == null) {
throw new ServletException("No " + tableName + " in context");
}
try {
BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
for (;;) {
final String strongName = r.readLine();
if (strongName == null) {
break;
}
if (strongName.startsWith("css ")) {
css.add(strongName.substring("css ".length()));
continue;
}
Map<String, String> selections = new LinkedHashMap<String, String>();
for (;;) {
String permutation = r.readLine();
if (permutation == null || permutation.isEmpty()) {
break;
}
int eq = permutation.indexOf('=');
if (eq < 0) {
throw new ServletException(tableName + " has malformed content");
}
String k = permutation.substring(0, eq).trim();
String v = permutation.substring(eq + 1);
Rule rule = get(k);
if (!ruleOrder.contains(rule)) {
ruleOrder.add(rule);
}
if (selections.put(k, v) != null) {
throw new ServletException("Table " + tableName
+ " has multiple values for " + k + " within permutation "
+ strongName);
}
}
String cacheHtml = strongName + ".cache.html";
String[] values = new String[ruleOrder.size()];
for (int i = 0; i < values.length; i++) {
values[i] = selections.get(ruleOrder.get(i).getName());
}
permutations.add(new Permutation(this, cacheHtml, values));
}
} finally {
in.close();
}
}
private Rule get(final String name) {
Rule r = rulesByName.get(name);
if (r == null) {
r = new ClientSideRule(name);
register(r);
}
return r;
}
/** @return name of the module (within the application context). */
public String getModuleName() {
return moduleName;
}
/** @return all possible permutations */
public List<Permutation> getPermutations() {
return Collections.unmodifiableList(permutations);
}
/**
* Select the permutation that best matches the browser request.
*
* @param req current request.
* @return the selected permutation; null if no permutation can be fit to the
* request and the standard {@code nocache.js} loader must be used.
*/
public Permutation select(HttpServletRequest req) {
final String[] values = new String[ruleOrder.size()];
for (int i = 0; i < values.length; i++) {
final String value = ruleOrder.get(i).select(req);
if (value == null) {
// If the rule returned null it doesn't know how to compute
// the value for this HTTP request. Since we can't do that
// defer to JavaScript by not picking a permutation.
//
return null;
}
values[i] = value;
}
for (Permutation p : permutations) {
if (p.matches(values)) {
return p;
}
}
return null;
}
Collection<String> getCSS() {
return css;
}
}