| /* |
| * Copyright 2009 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.gwt.uibinder.attributeparsers; |
| |
| import com.google.gwt.core.ext.typeinfo.JType; |
| import com.google.gwt.uibinder.rebind.FieldManager; |
| |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Deals with field references, e.g. the bits in braces here: <code><div |
| * class="{style.enabled} fancy {style.impressive}" /></code>, by converting |
| * them to java expressions (with the help of a |
| * {@link com.google.gwt.uibinder.attributeparsers.FieldReferenceConverter.Delegate Delegate}). |
| * <p> |
| * A field reference is one or more segments separated by dots. The first |
| * segment is considered to be a reference to a ui field, and succeeding |
| * segments are method calls. So, <code>"{able.baker.charlie}"</code> becomes |
| * <code>"able.baker().charlie()"</code>. |
| * <p> |
| * A field reference starts with '{' and is followed immediately by a character |
| * that can legally start a java identifier—that is a letter, $, or |
| * underscore. Braces not followed by such a character are left in place. |
| * <p> |
| * For convenience when dealing with generated CssResources, field segments with |
| * dashes are converted to camel case. That is, {able.baker-charlie} is the same |
| * as {able.bakerCharlie} |
| * <p> |
| * Opening braces may be escape by doubling them. That is, "{{foo}" will |
| * converted to "{foo}", with no field reference detected. |
| */ |
| public class FieldReferenceConverter { |
| /** |
| * May be thrown by the {@link com.google.gwt.uibinder.attributeparsers.FieldReferenceConverter.Delegate Delegate} for badly |
| * formatted input. |
| */ |
| @SuppressWarnings("serial") |
| public static class IllegalFieldReferenceException extends RuntimeException { |
| } |
| |
| /** |
| * Responsible for the bits around and between the field references. May throw |
| * IllegalFieldReferenceException as it sees fit. |
| */ |
| interface Delegate { |
| /** |
| * Returns the type any parsed field references are expected to return. |
| */ |
| JType getType(); |
| |
| /** |
| * Called for fragment around and between field references. |
| * <p> |
| * Note that it will be called with empty strings if these surrounding bits |
| * are empty. E.g., "{style.enabled} fancy {style.impressive}" would call |
| * this method three times, with "", " fancy ", and "". |
| * <p> |
| * A string with no field references is treated as a single fragment, and |
| * causes a single call to this method. |
| */ |
| String handleFragment(String fragment) |
| throws IllegalFieldReferenceException; |
| |
| /** |
| * Called for each expanded field reference, to allow it to be stitched |
| * together with surrounding fragments. |
| */ |
| String handleReference(String reference) |
| throws IllegalFieldReferenceException; |
| } |
| |
| /** |
| * Used by {@link #hasFieldReferences}. Passthrough implementation that notes |
| * when handleReference has been called. |
| */ |
| private static final class Telltale implements |
| FieldReferenceConverter.Delegate { |
| boolean hasComputed = false; |
| |
| public JType getType() { |
| return null; |
| } |
| |
| public String handleFragment(String fragment) { |
| return fragment; |
| } |
| |
| public String handleReference(String reference) { |
| hasComputed = true; |
| return reference; |
| } |
| |
| public boolean hasComputed() { |
| return hasComputed; |
| } |
| } |
| |
| private static final Pattern BRACES = Pattern.compile("[{]([^}]*)[}]"); |
| private static final Pattern LEGAL_FIRST_CHAR = Pattern.compile("^[$_a-zA-Z].*"); |
| |
| /** |
| * Returns true if the given string holds one or more field references. |
| */ |
| public static boolean hasFieldReferences(String string) { |
| Telltale telltale = new Telltale(); |
| new FieldReferenceConverter(null).convert(string, telltale); |
| return telltale.hasComputed(); |
| } |
| |
| private final CssNameConverter cssConverter = new CssNameConverter(); |
| private final FieldManager fieldManager; |
| |
| /** |
| * @param fieldManager to register parsed references with. May be null |
| */ |
| FieldReferenceConverter(FieldManager fieldManager) { |
| this.fieldManager = fieldManager; |
| } |
| |
| /** |
| * @throws IllegalFieldReferenceException if the delegate does |
| */ |
| public String convert(String in, Delegate delegate) { |
| StringBuilder b = new StringBuilder(); |
| int nextFindStart = 0; |
| int lastMatchEnd = 0; |
| |
| Matcher m = BRACES.matcher(in); |
| while (m.find(nextFindStart)) { |
| String fieldReference = m.group(1); |
| if (!legalFirstCharacter(fieldReference)) { |
| nextFindStart = m.start() + 2; |
| continue; |
| } |
| |
| String precedingFragment = in.substring(lastMatchEnd, m.start()); |
| precedingFragment = handleFragment(precedingFragment, delegate); |
| b.append(precedingFragment); |
| |
| if (fieldManager != null) { |
| fieldManager.registerFieldReference(fieldReference, delegate.getType()); |
| } |
| fieldReference = expandDots(fieldReference); |
| b.append(delegate.handleReference(fieldReference)); |
| nextFindStart = lastMatchEnd = m.end(); |
| } |
| |
| b.append(handleFragment(in.substring(lastMatchEnd), delegate)); |
| return b.toString(); |
| } |
| |
| private String expandDots(String value) { |
| StringBuilder b = new StringBuilder(); |
| String[] segments = value.split("[.]"); |
| |
| for (String segment : segments) { |
| segment = cssConverter.convertName(segment); |
| if (b.length() == 0) { |
| b.append(segment); // field name |
| } else { |
| b.append(".").append(segment).append("()"); |
| } |
| } |
| return b.toString(); |
| } |
| |
| private String handleFragment(String fragment, Delegate delegate) { |
| fragment = fragment.replace("{{", "{"); |
| return delegate.handleFragment(fragment); |
| } |
| |
| private boolean legalFirstCharacter(String fieldReference) { |
| return LEGAL_FIRST_CHAR.matcher(fieldReference).matches(); |
| } |
| } |