| /******************************************************************************* |
| * Copyright (c) 2016 IBM Corporation. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * This is an implementation of an early-draft specification developed under the Java |
| * Community Process (JCP) and is made available for testing and evaluation purposes |
| * only. The code is not compatible with any specification of the JCP. |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.lookup; |
| |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ast.ExportReference; |
| import org.eclipse.jdt.internal.compiler.ast.ModuleDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.ModuleReference; |
| import org.eclipse.jdt.internal.compiler.ast.TypeReference; |
| import org.eclipse.jdt.internal.compiler.env.IModule; |
| import org.eclipse.jdt.internal.compiler.env.IModuleLocation; |
| import org.eclipse.jdt.internal.compiler.env.INameEnvironment; |
| import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; |
| import org.eclipse.jdt.internal.compiler.util.JimageUtil; |
| |
| public abstract class ModuleEnvironment implements INameEnvironment { |
| /* |
| * Keeps track of packages mapped to modules |
| * Knows how to get modules |
| * Understands modules |
| * Understand module dependencies and restrictions |
| * Given a ModuleDeclaration, creates an IModule |
| * TODO: This should kick-in only when source level is >= 9 |
| */ |
| public static final char[] UNNAMED = "UNNAMED".toCharArray(); //$NON-NLS-1$ |
| public static final IModule UNNAMED_MODULE = new IModule() { |
| @Override |
| public char[][] uses() { |
| return null; |
| } |
| @Override |
| public IModuleReference[] requires() { |
| return null; |
| } |
| @Override |
| public IService[] provides() { |
| return null; |
| } |
| @Override |
| public char[] name() { |
| return UNNAMED; |
| } |
| @Override |
| public IPackageExport[] exports() { |
| return null; |
| } |
| @Override |
| public String toString() { |
| return new String(UNNAMED); |
| } |
| }; |
| public static IModule[] UNNAMED_MODULE_ARRAY = new IModule[]{UNNAMED_MODULE}; |
| private HashMap<String, IModule> modulesCache = null; |
| private static HashMap<IModuleLocation, IModule> ModuleLocationMap = new HashMap<>(); |
| |
| public ModuleEnvironment() { |
| this.modulesCache = new HashMap<>(); |
| } |
| |
| public NameEnvironmentAnswer findType(char[][] compoundTypeName, char[] client) { |
| NameEnvironmentAnswer answer = findType(compoundTypeName, getVisibleModules(client)); |
| return answer; |
| } |
| |
| public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName, char[] client) { |
| NameEnvironmentAnswer answer = findType(typeName, packageName, getVisibleModules(client)); |
| char[] module = null; |
| if(answer == null || (module = answer.moduleName()) == null || |
| CharOperation.equals(module, JimageUtil.JAVA_BASE_CHAR)) { |
| return answer; |
| } |
| return returnAnswerAfterValidation(packageName, answer, client); |
| } |
| |
| protected NameEnvironmentAnswer returnAnswerAfterValidation(char[][] packageName, NameEnvironmentAnswer answer, char[] client) { |
| if (isPackageVisible(CharOperation.concatWith(packageName, '.'), answer.moduleName(), client)) { |
| return answer; |
| } |
| return null; |
| } |
| |
| public boolean isPackage(char[][] parentPackageName, char[] packageName, char[] client) { |
| return isPackage(parentPackageName, packageName, getVisibleModules(client)); |
| } |
| |
| public abstract NameEnvironmentAnswer findType(char[][] compoundTypeName, IModule[] modules); |
| |
| public abstract NameEnvironmentAnswer findType(char[] typeName, char[][] packageName, IModule[] modules); |
| |
| public abstract boolean isPackage(char[][] parentPackageName, char[] packageName, IModule[] module); |
| |
| // public abstract IModule getModule(String name); |
| // { |
| //// IModule mod = this.modulesCache.get(name); |
| //// if (mod == null) { |
| //// |
| //// } |
| //// return mod; |
| // for () |
| // } |
| |
| public boolean isPackageVisible(char[] packageName, char[] sourceName, char[] clientName) { |
| boolean clientIsUnnamed = clientName == null || clientName == UNNAMED; |
| if (clientIsUnnamed) |
| return true; // Unnamed module can read every module |
| if (sourceName == null || sourceName == UNNAMED) |
| return clientIsUnnamed; // Unnamed module cannot be read by any named module |
| |
| if (CharOperation.equals(sourceName, clientName)) |
| return true; |
| |
| IModule source = getModule(sourceName); |
| IModule client = getModule(clientName); |
| if (client != null) { |
| IModule.IModuleReference[] requires = client.requires(); |
| if (requires != null && requires.length > 0) { |
| for (IModule.IModuleReference ref : requires) { |
| IModule refModule = getModule(ref.name()); |
| if (refModule == null) |
| continue; |
| if (refModule == source && isPackageExportedTo(refModule, packageName, client)) { |
| return true; |
| } |
| Set<IModule> set = new LinkedHashSet<>(); |
| collectAllVisibleModules(refModule, set, true); |
| IModule[] targets = set.toArray(new IModule[set.size()]); |
| for (IModule iModule : targets) { |
| if (iModule == source && isPackageExportedTo(iModule, packageName, client)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| private boolean isPackageExportedTo(IModule module, char[] pack, IModule client) { |
| IModule.IPackageExport[] exports = module.exports(); |
| if (exports != null && exports.length > 0) { |
| for (IModule.IPackageExport iPackageExport : exports) { |
| if (CharOperation.equals(iPackageExport.name(), pack)) { |
| char[][] exportedTo = iPackageExport.exportedTo(); |
| if (exportedTo == null || exportedTo.length == 0) { |
| return true; |
| } |
| for (char[] cs : exportedTo) { |
| if (CharOperation.equals(cs, client.name())) { |
| return true; |
| } |
| } |
| |
| } |
| } |
| } |
| return false; |
| } |
| |
| public IModule[] getVisibleModules(char[] mod) { |
| IModule[] targets = null; |
| if (mod != null && !CharOperation.equals(mod, UNNAMED)) { |
| Set<IModule> set = new LinkedHashSet<>(); |
| IModule client = getModule(JimageUtil.JAVA_BASE.toCharArray()); |
| if (client != null) set.add(client); |
| client = getModule(mod); |
| if (client != null) { |
| set.add(client); |
| collectAllVisibleModules(client, set, false); |
| targets = set.toArray(new IModule[set.size()]); |
| } |
| } else { |
| return UNNAMED_MODULE_ARRAY; |
| } |
| return targets; |
| } |
| |
| private void collectAllVisibleModules(IModule module, Set<IModule> targets, boolean onlyPublic) { |
| if (module != null) { |
| IModule.IModuleReference[] requires = module.requires(); |
| if (requires != null && requires.length > 0) { |
| for (IModule.IModuleReference ref : requires) { |
| IModule refModule = getModule(ref.name()); |
| if (refModule != null) { |
| if (!onlyPublic || ref.isPublic()) { |
| targets.add(refModule); |
| collectAllVisibleModules(refModule, targets, true); |
| } |
| } |
| } |
| } |
| } |
| } |
| /** |
| * Returns the module with the given name from the name environment. |
| * |
| * @param name the name of the module to lookup |
| * @return the module with the given name |
| */ |
| // public IModule getModule(final char[] name) { |
| //// if (name == null) return null; |
| //// String mod = new String(name); |
| //// IModule module = this.modulesCache.get(mod); |
| //// return module; |
| // return name == null ? null : getModule(CharOperation.charToString(name)); |
| // } |
| /** |
| * Accepts the given module to be served later on requests. If |
| * any older copies of module already present, they will be |
| * overwritten by the new one. |
| * |
| * @param mod the module to be stored in memory |
| */ |
| public void acceptModule(IModule mod, IModuleLocation location) { |
| IModule existing = ModuleEnvironment.ModuleLocationMap.get(location); |
| if (existing != null) { |
| if (existing.equals(mod)) |
| return; |
| else { |
| // Report problem and ignore the duplicate |
| } |
| } |
| String name = new String(mod.name()); |
| this.modulesCache.put(name, mod); |
| ModuleEnvironment.ModuleLocationMap.put(location, mod); |
| } |
| |
| @Override |
| public IModule getModule(IModuleLocation location) { |
| IModule module = ModuleEnvironment.ModuleLocationMap.get(location); |
| if (module == null) |
| return null; |
| String modName = new String(module.name()); |
| if (this.modulesCache.get(modName) == null) { |
| this.modulesCache.put(modName, module); |
| } |
| return module; |
| } |
| public static Module createModule(ModuleDeclaration module) { |
| Module mod = new Module(); |
| mod.name = module.moduleName; |
| if (module.requiresCount > 0) { |
| ModuleReference[] refs = module.requires; |
| mod.requires = new ModuleReferenceImpl[refs.length]; |
| for (int i = 0; i < refs.length; i++) { |
| mod.requires[i] = new ModuleReferenceImpl(); |
| mod.requires[i].name = CharOperation.concatWith(refs[i].tokens, '.'); |
| mod.requires[i].isPublic = refs[i].isPublic(); |
| } |
| } else { |
| mod.requires = new ModuleReferenceImpl[0]; |
| } |
| if (module.exportsCount > 0) { |
| ExportReference[] refs = module.exports; |
| mod.exports = new PackageExport[refs.length]; |
| for (int i = 0; i < refs.length; i++) { |
| PackageExport exp = createPackageExport(refs, i); |
| mod.exports[i] = exp; |
| } |
| } else { |
| mod.exports = new PackageExport[0]; |
| } |
| if (module.usesCount > 0) { |
| TypeReference[] uses = module.uses; |
| mod.uses = new char[uses.length][]; |
| for(int i = 0; i < uses.length; i++) { |
| mod.uses[i] = CharOperation.concatWith(uses[i].getTypeName(), '.'); |
| } |
| } |
| if (module.servicesCount > 0) { |
| TypeReference[] services = module.interfaces; |
| TypeReference[] with = module.implementations; |
| mod.provides = new Service[module.servicesCount]; |
| for (int i = 0; i < module.servicesCount; i++) { |
| mod.provides[i] = createService(services[i], with[i]); |
| } |
| } |
| return mod; |
| } |
| |
| private static PackageExport createPackageExport(ExportReference[] refs, int i) { |
| ExportReference ref = refs[i]; |
| PackageExport exp = new PackageExport(); |
| exp.pack = CharOperation.concatWith(ref.tokens, '.'); |
| ModuleReference[] imp = ref.targets; |
| if (imp != null) { |
| exp.exportedTo = new char[imp.length][]; |
| for(int j = 0; j < imp.length; j++) { |
| exp.exportedTo = imp[j].tokens; |
| } |
| } |
| return exp; |
| } |
| private static Service createService(TypeReference service, TypeReference with) { |
| Service ser = new Service(); |
| ser.provides = CharOperation.concatWith(service.getTypeName(), '.'); |
| ser.with = CharOperation.concatWith(with.getTypeName(), '.'); |
| return ser; |
| } |
| static class Module implements IModule { |
| char[] name; |
| ModuleReferenceImpl[] requires; |
| PackageExport[] exports; |
| char[][] uses; |
| Service[] provides; |
| @Override |
| public char[] name() { |
| return this.name; |
| } |
| @Override |
| public IModule.IModuleReference[] requires() { |
| return this.requires; |
| } |
| @Override |
| public IPackageExport[] exports() { |
| return this.exports; |
| } |
| @Override |
| public char[][] uses() { |
| return this.uses; |
| } |
| @Override |
| public IService[] provides() { |
| return this.provides(); |
| } |
| public boolean equals(Object o) { |
| if (this == o) |
| return true; |
| if (!(o instanceof IModule)) |
| return false; |
| IModule mod = (IModule) o; |
| if (!CharOperation.equals(this.name, mod.name())) |
| return false; |
| return Arrays.equals(this.requires, mod.requires()); |
| } |
| @Override |
| public int hashCode() { |
| int result = 17; |
| int c = this.name.hashCode(); |
| result = 31 * result + c; |
| c = Arrays.hashCode(this.requires); |
| result = 31 * result + c; |
| return result; |
| } |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(getClass().getName()); |
| toStringContent(buffer); |
| return buffer.toString(); |
| } |
| protected void toStringContent(StringBuffer buffer) { |
| buffer.append("\nmodule "); //$NON-NLS-1$ |
| buffer.append(this.name).append(' '); |
| buffer.append('{').append('\n'); |
| if (this.requires != null) { |
| for(int i = 0; i < this.requires.length; i++) { |
| buffer.append("\trequires "); //$NON-NLS-1$ |
| if (this.requires[i].isPublic) { |
| buffer.append(" public "); //$NON-NLS-1$ |
| } |
| buffer.append(this.requires[i].name); |
| buffer.append(';').append('\n'); |
| } |
| } |
| if (this.exports != null) { |
| buffer.append('\n'); |
| for(int i = 0; i < this.exports.length; i++) { |
| buffer.append("\texports "); //$NON-NLS-1$ |
| buffer.append(this.exports[i].toString()); |
| } |
| } |
| if (this.uses != null) { |
| buffer.append('\n'); |
| for (char[] cs : this.uses) { |
| buffer.append(cs); |
| buffer.append(';').append('\n'); |
| } |
| } |
| if (this.provides != null) { |
| buffer.append('\n'); |
| for(Service ser : this.provides) { |
| buffer.append(ser.toString()); |
| } |
| } |
| buffer.append('\n').append('}').toString(); |
| } |
| } |
| static class ModuleReferenceImpl implements IModule.IModuleReference { |
| char[] name; |
| boolean isPublic = false; |
| @Override |
| public char[] name() { |
| return this.name; |
| } |
| @Override |
| public boolean isPublic() { |
| return this.isPublic; |
| } |
| public boolean equals(Object o) { |
| if (this == o) |
| return true; |
| if (!(o instanceof IModule.IModuleReference)) |
| return false; |
| IModule.IModuleReference mod = (IModule.IModuleReference) o; |
| if (this.isPublic != mod.isPublic()) |
| return false; |
| return CharOperation.equals(this.name, mod.name(), false); |
| } |
| @Override |
| public int hashCode() { |
| return this.name.hashCode(); |
| } |
| } |
| static class PackageExport implements IModule.IPackageExport { |
| char[] pack; |
| char[][] exportedTo; |
| @Override |
| public char[] name() { |
| return this.pack; |
| } |
| |
| @Override |
| public char[][] exportedTo() { |
| return this.exportedTo; |
| } |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append(this.pack); |
| if (this.exportedTo != null) { |
| for (char[] cs : this.exportedTo) { |
| buffer.append(cs); |
| } |
| } |
| buffer.append(';'); |
| return buffer.toString(); |
| } |
| } |
| static class Service implements IModule.IService { |
| char[] provides; |
| char[] with; |
| @Override |
| public char[] name() { |
| return this.provides; |
| } |
| |
| @Override |
| public char[] with() { |
| return this.with; |
| } |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("provides"); //$NON-NLS-1$ |
| buffer.append(this.provides); |
| buffer.append(" with "); //$NON-NLS-1$ |
| buffer.append(this.with); |
| buffer.append(';'); |
| return buffer.toString(); |
| } |
| } |
| } |