/*
* 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.devtools.j2objc.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.j2objc.J2ObjC;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
/**
* Manages class and method name mappings.
*/
public class Mappings {
/**
* We convert all the String constructor invocations to factory method invocations because we want
* to avoid calling [NSString alloc].
*/
public static final ImmutableMap<String, String> STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS =
ImmutableMap.<String, String>builder()
.put("java.lang.String.<init>()V", "string")
.put("java.lang.String.<init>(Ljava/lang/String;)V", "stringWithString:")
.put("java.lang.String.<init>([B)V", "java_stringWithBytes:")
.put(
"java.lang.String.<init>([BLjava/lang/String;)V", "java_stringWithBytes:charsetName:")
.put(
"java.lang.String.<init>([BLjava/nio/charset/Charset;)V",
"java_stringWithBytes:charset:")
.put("java.lang.String.<init>([BI)V", "java_stringWithBytes:hibyte:")
.put("java.lang.String.<init>([BII)V", "java_stringWithBytes:offset:length:")
.put("java.lang.String.<init>([BIII)V", "java_stringWithBytes:hibyte:offset:length:")
.put(
"java.lang.String.<init>([BIILjava/lang/String;)V",
"java_stringWithBytes:offset:length:charsetName:")
.put(
"java.lang.String.<init>([BIILjava/nio/charset/Charset;)V",
"java_stringWithBytes:offset:length:charset:")
.put("java.lang.String.<init>([C)V", "java_stringWithCharacters:")
.put("java.lang.String.<init>([CII)V", "java_stringWithCharacters:offset:length:")
.put("java.lang.String.<init>([III)V", "java_stringWithInts:offset:length:")
.put(
"java.lang.String.<init>(Ljava/lang/StringBuffer;)V",
"java_stringWithJavaLangStringBuffer:")
.put(
"java.lang.String.<init>(Ljava/lang/StringBuilder;)V",
"java_stringWithJavaLangStringBuilder:")
.build();
private static final String JRE_MAPPINGS_FILE = "JRE.mappings";
private final Map<String, String> classMappings = new HashMap<>();
private final Map<String, String> methodMappings = new HashMap<>();
{
methodMappings.putAll(STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS);
}
public ImmutableMap<String, String> getClassMappings() {
return ImmutableMap.copyOf(classMappings);
}
public ImmutableMap<String, String> getMethodMappings() {
return ImmutableMap.copyOf(methodMappings);
}
@VisibleForTesting
void addClass(String key, String name) {
classMappings.put(key, name);
}
public void addMappingsFiles(String[] filenames) throws IOException {
for (String filename : filenames) {
if (!filename.isEmpty()) {
addMappingsProperties(FileUtil.loadProperties(filename));
}
}
}
public void addJreMappings() throws IOException {
InputStream stream = J2ObjC.class.getResourceAsStream(JRE_MAPPINGS_FILE);
addMappingsProperties(FileUtil.loadProperties(stream));
}
private void addMappingsProperties(Properties mappings) {
Enumeration<?> keyIterator = mappings.propertyNames();
while (keyIterator.hasMoreElements()) {
String key = (String) keyIterator.nextElement();
if (key.indexOf('(') > 0) {
// All method mappings have parentheses characters, classes don't.
String iosMethod = mappings.getProperty(key);
addMapping(methodMappings, key, iosMethod, "method mapping");
} else {
String iosClass = mappings.getProperty(key);
addMapping(classMappings, key, iosClass, "class mapping");
}
}
}
/**
* Adds a class, method or package-prefix property to its map, reporting an error
* if that mapping was previously specified.
*/
private static void addMapping(Map<String, String> map, String key, String value, String kind) {
String oldValue = map.put(key, value);
if (oldValue != null && !oldValue.equals(value)) {
ErrorUtil.error(kind + " redefined; was \"" + oldValue + ", now " + value);
}
}
public static String getMethodKey(ExecutableElement method, TypeUtil typeUtil) {
StringBuilder sb = new StringBuilder();
sb.append(typeUtil.elementUtil().getBinaryName(ElementUtil.getDeclaringClass(method)));
sb.append('.');
sb.append(ElementUtil.getName(method));
sb.append('(');
for (VariableElement param : method.getParameters()) {
sb.append(typeUtil.getSignatureName(param.asType()));
}
sb.append(')');
sb.append(typeUtil.getSignatureName(method.getReturnType()));
return sb.toString();
}
}