/* * Copyright 2008 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.inject.rebind.util; import com.google.gwt.inject.rebind.output.FragmentPackageName; import com.google.gwt.inject.rebind.util.Preconditions; import com.google.inject.Key; import com.google.inject.TypeLiteral; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; /** * Helper to generate various names for members of a {@code Ginjector} * implementation. */ public class NameGenerator { private class CacheKey { private final String prefix; private final Key<?> key; CacheKey(String prefix, Key<?> key) { this.prefix = prefix; this.key = key; } @Override public boolean equals(Object obj) { if (obj instanceof CacheKey) { CacheKey other = (CacheKey) obj; return other.prefix.equals(prefix) && other.key.equals(key); } return false; } @Override public int hashCode() { return prefix.hashCode() * 31 + key.hashCode(); } } /** * "Mangled key name" cache: Key -> mangled name */ private final Map<CacheKey, String> methodKeyCache = new LinkedHashMap<CacheKey, String>(); /** * Map of known method names. */ private final Set<String> methodNames = new LinkedHashSet<String>(); /** * Returns the name of an assisted injection helper method. */ public String getAssistedInjectMethodName(Key<?> factoryKey, String methodName) { return mangle("assistedInject_" + methodName, factoryKey); } /** * Returns the name of a getter for a child injector. */ public String getChildInjectorGetterMethodName(String childInjectorClassName) { return convertToValidMemberName("getChild_" + childInjectorClassName); } /** * Returnst he name of a getter for an injector fragment. */ public String getFragmentGetterMethodName(FragmentPackageName fragmentPackageName) { return "getFragment_" + fragmentPackageName.toString().replace(".", "_"); } /** * Returns the key's getter method name. The method with that name can be * called to retrieve an instance of the type described by the key. * * @return getter method name */ public String getGetterMethodName(Key<?> key) { return mangle("get_", key); } /** * Computes the name of a single fragment of a Ginjector. * * @param injectorClassName the simple name of the injector's class (not * including its package) */ public String getFragmentClassName(String injectorClassName, FragmentPackageName fragmentPackageName) { // Sanity check. Preconditions.checkArgument(!injectorClassName.contains("."), "The injector class must be a simple name, but it was \"%s\"", injectorClassName); // Note that the fragment package name is not actually included in the // fragment. This reduces the length of the fragment's class name, which is // important because some systems have small limits on the maximum length of // a file (e.g., ~256 characters). However, it means that other parts of // Gin must reference the fragment using its canonical class name, to avoid // ambiguity. return injectorClassName + "_fragment"; } /** * Computes the canonical name (including package) of a single fragment of a * Ginjector. */ public String getFragmentCanonicalClassName(String injectorClassName, FragmentPackageName fragmentPackageName) { return fragmentPackageName + "." + getFragmentClassName(injectorClassName, fragmentPackageName); } /** * Computes the field name of a single fragment of an injector. */ public String getFragmentFieldName(FragmentPackageName fragmentPackageName) { return convertToValidMemberName("fieldFragment_" + fragmentPackageName); } /** * Computes the name of the field in which the Ginjector interface * implementation is stored. */ public String getGinjectorInterfaceFieldName() { return "fieldGinjectorInterface"; } /** * Computes the name of the method used to retrieve the Ginjector interface * implementation. */ public String getGinjectorInterfaceGetterMethodName() { return "getGinjectorInterface"; } /** * Returns the type's member inject method name. The method with that name * can be called with a single parameter to inject members of that parameter. * * @return member inject method name */ public String getMemberInjectMethodName(TypeLiteral<?> type) { return mangle("memberInject_", Key.get(type)); } /** * Returns the key's singleton field name. * * @return singleton field name */ public String getSingletonFieldName(Key<?> key) { return mangle("singleton_", key); } /** * Returns a new valid (i.e. unique) method name based on {@code base}. * * Note: Method names are considered "used" after being returned by this * method, whether they're actually used to write a new method or not. * * @param base base on which method name gets created * @return valid method name */ public String createMethodName(String base) { while (methodNames.contains(base)) { // TODO(schmitt): Make more efficient for repeated calls with same base? base += "_"; } methodNames.add(base); return base; } /** * Reserves the given name to prevent new methods to be created with it. * * @param name name to be reserved */ public void markAsUsed(String name) throws IllegalArgumentException { methodNames.add(name); } private String mangle(String prefix, Key<?> key) { CacheKey cacheKey = new CacheKey(prefix, key); String cached = methodKeyCache.get(cacheKey); if (cached != null) { return cached; } // TODO(bstoler): This algorithm is kinda crazy because the annotation // values are of unbounded length. One option // is to use mangled(type) + mangled(annotation type) + counter, where // counter is used just to distinguish different annotation values. String name = prefix + key.toString(); name = convertToValidMemberName(name); name = createMethodName(name); methodKeyCache.put(cacheKey, name); return name; } public String convertToValidMemberName(String name) { name = name.replaceAll("\\s+", "_"); name = name.replaceAll("[^\\p{Alnum}_]", "\\$"); return name; } // Static for access from enum. public static String replaceLast(String source, char toReplace, char with) { StringBuilder sb = new StringBuilder(source); int index = sb.lastIndexOf(String.valueOf(toReplace)); if (index != -1) { sb.setCharAt(index, with); } return sb.toString(); } }