How to change GWT Place URL from the default ":" to "/"?

By default, a GWT Place URL consists of the Place's simple class name (like "HelloPlace") followed by a colon (:) and the token returned by the PlaceTokenizer.

My question is how can I change ":" to be "/"?

Answers


I just made my own PlaceHistoryMapper that directly implements the interface instead of using AbstractPlaceHistoryMapper:

public class AppPlaceHistoryMapper implements PlaceHistoryMapper
{
    String delimiter = "/";

    @Override
    public Place getPlace(String token)
    {

        String[] tokens = token.split(delimiter, 2); 

            if (tokens[0].equals("HelloPlace"))
                 ...
    }

    @Override
    public String getToken(Place place)
    {
        if (place instanceof HelloPlace)
        {
            return "HelloPlace" + delimiter + whatever;
        }
        else ...
    }
}

It's certainly extra code to write, but you can control your url structure all in one place, and use slashes instead of colons!


Here is how to customize the delimiter, while using the standard GWT places. (PlaceHistoryMapper)

Nothing else needs to be changed; it works with the standard way of using Places and Tokenizers.

Insert into gwt.xml

 <generate-with
  class="com.google.gwt.place.rebind.CustomPlaceHistoryMapperGenerator">
  <when-type-assignable class="com.google.gwt.place.shared.PlaceHistoryMapper" />
 </generate-with>

Add CustomAbstractPlaceHistoryMapper

package com.siderakis.client.mvp;

import com.google.gwt.place.impl.AbstractPlaceHistoryMapper;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceTokenizer;

public abstract class CustomAbstractPlaceHistoryMapper extends AbstractPlaceHistoryMapper {
 public final static String DELIMITER = "/";

 public static class CustomPrefixAndToken extends PrefixAndToken {
  public CustomPrefixAndToken(String prefix, String token) {
   super(prefix, token);
   assert prefix != null && !prefix.contains(DELIMITER);
  }

  @Override
  public String toString() {
   return (prefix.length() == 0) ? token : prefix + DELIMITER + token;
  }

 }

 @Override
 public Place getPlace(String token) {
  int colonAt = token.indexOf(DELIMITER);
  String initial;
  String rest;
  if (colonAt >= 0) {
   initial = token.substring(0, colonAt);
   rest = token.substring(colonAt + 1);
  } else {
   initial = "";
   rest = token;
  }
  PlaceTokenizer tokenizer = getTokenizer(initial);
  if (tokenizer != null) {
   return tokenizer.getPlace(rest);
  }
  return null;
 }

}

Add CustomPlaceHistoryMapperGenerator

/*
 * Copyright 2010 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.place.rebind;

import java.io.PrintWriter;

import com.siderakis.client.mvp.CustomAbstractPlaceHistoryMapper;
import com.siderakis.client.mvp.CustomAbstractPlaceHistoryMapper.CustomPrefixAndToken;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceTokenizer;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * Generates implementations of
 * {@link com.google.gwt.place.shared.PlaceHistoryMapper PlaceHistoryMapper}.
 */
public class CustomPlaceHistoryMapperGenerator extends Generator {
  private PlaceHistoryGeneratorContext context;

  @Override
  public String generate(TreeLogger logger, GeneratorContext generatorContext,
      String interfaceName) throws UnableToCompleteException {

    context = PlaceHistoryGeneratorContext.create(logger,
        generatorContext.getTypeOracle(), interfaceName);

    if (context == null) {
      return null;
    }

    PrintWriter out = generatorContext.tryCreate(logger, context.packageName,
        context.implName);

    if (out != null) {
      generateOnce(generatorContext, context, out);
    }

    return context.packageName + "." + context.implName;
  }

  private void generateOnce(GeneratorContext generatorContext, PlaceHistoryGeneratorContext context,
      PrintWriter out) throws UnableToCompleteException {

    TreeLogger logger = context.logger.branch(TreeLogger.DEBUG, String.format(
        "Generating implementation of %s", context.interfaceType.getName()));
    ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
        context.packageName, context.implName);

    String superClassName = String.format("%s<%s>",
        CustomAbstractPlaceHistoryMapper.class.getSimpleName(),
        context.factoryType == null ? "Void" : context.factoryType.getName());
    f.setSuperclass(superClassName);
    f.addImplementedInterface(context.interfaceType.getName());

    f.addImport(CustomAbstractPlaceHistoryMapper.class.getName());
    f.addImport(context.interfaceType.getQualifiedSourceName());

    f.addImport(CustomAbstractPlaceHistoryMapper.class.getCanonicalName());
    if (context.factoryType != null) {
      f.addImport(context.factoryType.getQualifiedSourceName());
    }

    f.addImport(Place.class.getCanonicalName());
    f.addImport(PlaceTokenizer.class.getCanonicalName());
    f.addImport(CustomPrefixAndToken.class.getCanonicalName());

    f.addImport(GWT.class.getCanonicalName());

    SourceWriter sw = f.createSourceWriter(generatorContext, out);
    sw.println();

    writeGetPrefixAndToken(context, sw);
    sw.println();

    writeGetTokenizer(context, sw);
    sw.println();

    sw.outdent();
    sw.println("}");
    generatorContext.commit(logger, out);
  }

  private void writeGetPrefixAndToken(PlaceHistoryGeneratorContext context,
      SourceWriter sw) throws UnableToCompleteException {
    sw.println("protected CustomPrefixAndToken getPrefixAndToken(Place newPlace) {");
    sw.indent();
    for (JClassType placeType : context.getPlaceTypes()) {
      String placeTypeName = placeType.getQualifiedSourceName();
      String prefix = context.getPrefix(placeType);

      sw.println("if (newPlace instanceof " + placeTypeName + ") {");
      sw.indent();
      sw.println(placeTypeName + " place = (" + placeTypeName + ") newPlace;");

      JMethod getter = context.getTokenizerGetter(prefix);
      if (getter != null) {
        sw.println(String.format("return new CustomPrefixAndToken(\"%s\", "
            + "factory.%s().getToken(place));", escape(prefix),
            getter.getName()));
      } else {
        sw.println(String.format(
            "PlaceTokenizer<%s> t = GWT.create(%s.class);", placeTypeName,
            context.getTokenizerType(prefix).getQualifiedSourceName()));
        sw.println(String.format("return new CustomPrefixAndToken(\"%s\", "
            + "t.getToken((%s) place));", escape(prefix), placeTypeName));
      }

      sw.outdent();
      sw.println("}");
    }

    sw.println("return null;");
    sw.outdent();
    sw.println("}");
  }

  private void writeGetTokenizer(PlaceHistoryGeneratorContext context,
      SourceWriter sw) throws UnableToCompleteException {
    sw.println("protected PlaceTokenizer getTokenizer(String prefix) {");
    sw.indent();

    for (String prefix : context.getPrefixes()) {
      JMethod getter = context.getTokenizerGetter(prefix);

      sw.println("if (\"" + escape(prefix) + "\".equals(prefix)) {");
      sw.indent();

      if (getter != null) {
        sw.println("return factory." + getter.getName() + "();");
      } else {
        sw.println(String.format("return GWT.create(%s.class);",
            context.getTokenizerType(prefix).getQualifiedSourceName()));
      }

      sw.outdent();
      sw.println("}");
    }

    sw.println("return null;");
    sw.outdent();
    sw.println("}");
    sw.outdent();
  }

}

Good question. The problem is, that this is hard-coded into AbstractPlaceHistoryMapper:

AbstractPlaceHistoryMapper.PrefixAndToken.toString():

return (prefix.length() == 0) ? token : prefix + ":" + token;

AbstractPlaceHistoryMapper.getPlace(String token):

int colonAt = token.indexOf(':');
...

And AbstractPlaceHistoryMapper is hard-coded into PlaceHistoryMapperGenerator.

It would probably be possible to exchange the generator by supplying your own module xml file, and reconfiguring the binding, but overall, I would consider this as "basically not configurable". (But see Riley's answer for a good alternative without declarative Tokenizer configuration!)


)

I really appreciated the answers, thanks SO for making me optimize my time (I was following in debugger where my getToken() method was called, and it's getting through listeners, handlers, magicians and funny stuff like that :-)

So thinking of the HistoryMapper is perfect, but the solution if you want to add a crawler is a lot simpler since you just need to add an extra '!' after the bookmark hash #!

So it is enough to simply decorate the original result with an extra character.

public class PlaceHistoryMapperDecorator implements PlaceHistoryMapper {
private static final String CRAWLER_PREFIX = "!";
protected PlaceHistoryMapper delegateHistoryMapper;

public PlaceHistoryMapperDecorator(PlaceHistoryMapper delegateHistoryMapper) {
    this.delegateHistoryMapper = delegateHistoryMapper;
}

@Override
public Place getPlace(String token) {
    String cleanToken = token;
    if (token.startsWith(CRAWLER_PREFIX))
        cleanToken = token.substring(CRAWLER_PREFIX.length());
    else {
        if (token.length() > 0)
            System.err.println("there might be an error: can't find crawler prefix in " + token);
    }
    return delegateHistoryMapper.getPlace(cleanToken);
}

@Override
public String getToken(Place place) {
    return CRAWLER_PREFIX + delegateHistoryMapper.getToken(place);
}

}

Then you pass that new instance to your PlaceHistoryHandler and that's it

        PlaceHistoryMapperDecorator historyMapperDecorator = new PlaceHistoryMapperDecorator((PlaceHistoryMapper) GWT.create(AppPlaceHistoryMapper.class));
    PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapperDecorator);

I tested it before posting this message, it works fine :-)


Need Your Help

Xcode : The file “XXX.entitlements” couldn’t be opened because there is no such file

ios xcode

Cause my old machine was damaged so I copied the project to another machine and build&amp;run it but xcode return error :

Make a textarea all caps?

javascript html css

I am trying to make a textarea that only will type in caps, even if the user isn't holding down shift or has caps lock on. Ideally this would accept the input no matter what and just automatically ...