/*
* 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.Lists;
import com.google.common.collect.Maps;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import javax.lang.model.element.PackageElement;
/**
* Class that creates and stores the prefixes associated with Java packages.
* Prefixes can be defined in several ways:
* <ul>
* <li>Using a --prefix command-line flag,</li>
* <li>In a properties file specified by a --prefixes command-line flag,</li>
* <li>By an ObjectiveCName annotation in a package-info.java source file, or</li>
* <li>By camel-casing the package name (default).
* </ul>
*
* Command-line wildcard flags (either separately or in a properties file) are
* also supported, which map multiple packages to a single prefix. For example,
* 'com.google.devtools.j2objc.*=J2C' specifies that all translator classes have
* a J2C prefix, but not the com.google.j2objc.annotations classes. Wildcard
* declarations are matched in the order they are declared.
*
* @author Tom Ball
*/
public final class PackagePrefixes {
private final PackageInfoLookup packageLookup;
private Map<String, String> mappedPrefixes = Maps.newHashMap();
// A key array is used so that wildcards are checked in declared order.
// There is one wildcard value for each key, enforced within this class.
// Too bad there's no available equivalent to android.util.ArrayMap.
private List<Pattern> wildcardKeys = Lists.newArrayList();
private List<String> wildcardValues = Lists.newArrayList();
public PackagePrefixes(PackageInfoLookup packageLookup) {
this.packageLookup = packageLookup;
}
@VisibleForTesting
String getPrefix(String pkg) {
String value = mappedPrefixes.get(pkg);
if (value != null) {
return value;
}
for (int i = 0; i < wildcardKeys.size(); i++) {
Pattern p = wildcardKeys.get(i);
if (p.matcher(pkg).matches()) {
value = wildcardValues.get(i);
mappedPrefixes.put(pkg, value);
return value;
}
}
return null;
}
public void addPrefix(String pkg, String prefix) {
if (pkg == null || prefix == null) {
throw new IllegalArgumentException("null package or prefix specified");
}
if (pkg.contains("*")) {
String regex = wildcardToRegex(pkg);
for (int i = 0; i < wildcardKeys.size(); i++) {
if (regex.equals(wildcardKeys.get(i).toString())) {
String oldPrefix = wildcardValues.get(i);
if (!prefix.equals(oldPrefix)) {
ErrorUtil.error("package prefix redefined; was \"" + oldPrefix + ", now " + prefix);
}
return;
}
}
wildcardKeys.add(Pattern.compile(regex));
wildcardValues.add(prefix);
} else {
mappedPrefixes.put(pkg, prefix);
}
}
/**
* Return the prefix for a specified package. If a prefix was specified
* for the package, then that prefix is returned. Otherwise, a camel-cased
* prefix is created from the package name.
*/
public String getPrefix(PackageElement packageElement) {
if (packageElement == null) {
return "";
}
String packageName = packageElement.getQualifiedName().toString();
String prefix = getPrefix(packageName);
if (prefix != null) {
return prefix;
}
prefix = packageLookup.getObjectiveCName(packageName);
if (prefix == null) {
prefix = NameTable.camelCaseQualifiedName(packageName);
}
addPrefix(packageName, prefix);
return prefix;
}
/**
* Add a file map of packages to their respective prefixes, using the Properties file format.
*/
public void addPrefixesFile(String filename) throws IOException {
Properties props = new Properties();
FileInputStream fis = new FileInputStream(filename);
props.load(fis);
fis.close();
addPrefixProperties(props);
}
/**
* Add a set of package=prefix properties.
*/
public void addPrefixProperties(Properties props) {
for (String pkg : props.stringPropertyNames()) {
addPrefix(pkg, props.getProperty(pkg).trim());
}
}
@VisibleForTesting
static String wildcardToRegex(String s) {
if (s.endsWith(".*")) {
// Include root package in regex. For example, foo.bar.* needs to match
// foo.bar, foo.bar.mumble, etc.
String root = s.substring(0, s.length() - 2).replace(".", "\\.");
return UnicodeUtils.format("^(%s|%s\\..*)$", root, root);
}
return UnicodeUtils.format("^%s$", s.replace(".", "\\.").replace("\\*", ".*"));
}
}